Correctly type videojs player
This commit is contained in:
parent
0374b6b5cd
commit
f5fcd9f725
|
@ -69,7 +69,7 @@
|
|||
"@types/node": "^10.9.2",
|
||||
"@types/sanitize-html": "1.18.0",
|
||||
"@types/socket.io-client": "^1.4.32",
|
||||
"@types/video.js": "^7.2.5",
|
||||
"@types/video.js": "^7.3.3",
|
||||
"@types/webtorrent": "^0.107.0",
|
||||
"angular2-hotkeys": "^2.1.2",
|
||||
"angularx-qrcode": "1.6.4",
|
||||
|
@ -133,6 +133,7 @@
|
|||
"videojs-dock": "^2.0.2",
|
||||
"videojs-hotkeys": "^0.2.21",
|
||||
"videostream": "~3.2.1",
|
||||
"vtt.js": "^0.13.0",
|
||||
"webpack-bundle-analyzer": "^3.0.2",
|
||||
"webpack-cli": "^3.0.8",
|
||||
"webtorrent": "^0.107.16",
|
||||
|
|
|
@ -1,85 +1,12 @@
|
|||
// @ts-ignore
|
||||
import * as videojs from 'video.js'
|
||||
import { VideoJSComponentInterface } from '../peertube-videojs-typings'
|
||||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
import './pause-bezel'
|
||||
|
||||
function getPauseBezel () {
|
||||
return `
|
||||
<div class="vjs-bezels-pause">
|
||||
<div class="vjs-bezel" role="status" aria-label="Pause">
|
||||
<div class="vjs-bezel-icon">
|
||||
<svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%">
|
||||
<use class="vjs-svg-shadow" xlink:href="#vjs-id-1"></use>
|
||||
<path class="vjs-svg-fill" d="M 12,26 16,26 16,10 12,10 z M 21,26 25,26 25,10 21,10 z" id="vjs-id-1"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
const Plugin = videojs.getPlugin('plugin')
|
||||
|
||||
function getPlayBezel () {
|
||||
return `
|
||||
<div class="vjs-bezels-play">
|
||||
<div class="vjs-bezel" role="status" aria-label="Play">
|
||||
<div class="vjs-bezel-icon">
|
||||
<svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%">
|
||||
<use class="vjs-svg-shadow" xlink:href="#vjs-id-2"></use>
|
||||
<path class="vjs-svg-fill" d="M 12,26 18.5,22 18.5,14 12,10 z M 18.5,22 25,18 25,18 18.5,14 z" id="ytp-id-2"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
// @ts-ignore-start
|
||||
const Component = videojs.getComponent('Component')
|
||||
class PauseBezel extends Component {
|
||||
options_: any
|
||||
container: HTMLBodyElement
|
||||
|
||||
constructor (player: videojs.Player, options: any) {
|
||||
super(player, options)
|
||||
this.options_ = options
|
||||
|
||||
player.on('pause', (_: any) => {
|
||||
if (player.seeking() || player.ended()) return
|
||||
this.container.innerHTML = getPauseBezel()
|
||||
this.showBezel()
|
||||
})
|
||||
|
||||
player.on('play', (_: any) => {
|
||||
if (player.seeking()) return
|
||||
this.container.innerHTML = getPlayBezel()
|
||||
this.showBezel()
|
||||
})
|
||||
}
|
||||
|
||||
createEl () {
|
||||
const container = super.createEl('div', {
|
||||
className: 'vjs-bezels-content'
|
||||
})
|
||||
this.container = container
|
||||
container.style.display = 'none'
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
showBezel () {
|
||||
this.container.style.display = 'inherit'
|
||||
setTimeout(() => {
|
||||
this.container.style.display = 'none'
|
||||
}, 500) // matching the animation duration
|
||||
}
|
||||
}
|
||||
// @ts-ignore-end
|
||||
|
||||
videojs.registerComponent('PauseBezel', PauseBezel)
|
||||
|
||||
const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin')
|
||||
class BezelsPlugin extends Plugin {
|
||||
constructor (player: videojs.Player, options: any = {}) {
|
||||
super(player, options)
|
||||
|
||||
constructor (player: VideoJsPlayer, options?: videojs.ComponentOptions) {
|
||||
super(player)
|
||||
|
||||
this.player.ready(() => {
|
||||
player.addClass('vjs-bezels')
|
||||
|
@ -90,4 +17,5 @@ class BezelsPlugin extends Plugin {
|
|||
}
|
||||
|
||||
videojs.registerPlugin('bezels', BezelsPlugin)
|
||||
|
||||
export { BezelsPlugin }
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
|
||||
function getPauseBezel () {
|
||||
return `
|
||||
<div class="vjs-bezels-pause">
|
||||
<div class="vjs-bezel" role="status" aria-label="Pause">
|
||||
<div class="vjs-bezel-icon">
|
||||
<svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%">
|
||||
<use class="vjs-svg-shadow" xlink:href="#vjs-id-1"></use>
|
||||
<path class="vjs-svg-fill" d="M 12,26 16,26 16,10 12,10 z M 21,26 25,26 25,10 21,10 z" id="vjs-id-1"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
function getPlayBezel () {
|
||||
return `
|
||||
<div class="vjs-bezels-play">
|
||||
<div class="vjs-bezel" role="status" aria-label="Play">
|
||||
<div class="vjs-bezel-icon">
|
||||
<svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%">
|
||||
<use class="vjs-svg-shadow" xlink:href="#vjs-id-2"></use>
|
||||
<path class="vjs-svg-fill" d="M 12,26 18.5,22 18.5,14 12,10 z M 18.5,22 25,18 25,18 18.5,14 z" id="ytp-id-2"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
const Component = videojs.getComponent('Component')
|
||||
class PauseBezel extends Component {
|
||||
container: HTMLDivElement
|
||||
|
||||
constructor (player: VideoJsPlayer, options?: videojs.ComponentOptions) {
|
||||
super(player, options)
|
||||
|
||||
player.on('pause', (_: any) => {
|
||||
if (player.seeking() || player.ended()) return
|
||||
this.container.innerHTML = getPauseBezel()
|
||||
this.showBezel()
|
||||
})
|
||||
|
||||
player.on('play', (_: any) => {
|
||||
if (player.seeking()) return
|
||||
this.container.innerHTML = getPlayBezel()
|
||||
this.showBezel()
|
||||
})
|
||||
}
|
||||
|
||||
createEl () {
|
||||
this.container = super.createEl('div', {
|
||||
className: 'vjs-bezels-content'
|
||||
}) as HTMLDivElement
|
||||
|
||||
this.container.style.display = 'none'
|
||||
|
||||
return this.container
|
||||
}
|
||||
|
||||
showBezel () {
|
||||
this.container.style.display = 'inherit'
|
||||
|
||||
setTimeout(() => {
|
||||
this.container.style.display = 'none'
|
||||
}, 500) // matching the animation duration
|
||||
}
|
||||
}
|
||||
|
||||
videojs.registerComponent('PauseBezel', PauseBezel)
|
|
@ -1,7 +1,5 @@
|
|||
// FIXME: something weird with our path definition in tsconfig and typings
|
||||
// @ts-ignore
|
||||
import * as videojs from 'video.js'
|
||||
import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo, VideoJSComponentInterface } from '../peertube-videojs-typings'
|
||||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../peertube-videojs-typings'
|
||||
import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs'
|
||||
import { Events, Segment } from 'p2p-media-loader-core'
|
||||
import { timeToInt } from '../utils'
|
||||
|
@ -10,7 +8,7 @@ import { timeToInt } from '../utils'
|
|||
window['videojs'] = videojs
|
||||
require('@streamroot/videojs-hlsjs-plugin')
|
||||
|
||||
const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin')
|
||||
const Plugin = videojs.getPlugin('plugin')
|
||||
class P2pMediaLoaderPlugin extends Plugin {
|
||||
|
||||
private readonly CONSTANTS = {
|
||||
|
@ -37,12 +35,13 @@ class P2pMediaLoaderPlugin extends Plugin {
|
|||
|
||||
private networkInfoInterval: any
|
||||
|
||||
constructor (player: videojs.Player, options: P2PMediaLoaderPluginOptions) {
|
||||
super(player, options)
|
||||
constructor (player: VideoJsPlayer, options?: P2PMediaLoaderPluginOptions) {
|
||||
super(player)
|
||||
|
||||
this.options = options
|
||||
|
||||
if (!videojs.Html5Hlsjs) {
|
||||
// FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080
|
||||
if (!(videojs as any).Html5Hlsjs) {
|
||||
const message = 'HLS.js does not seem to be supported.'
|
||||
console.warn(message)
|
||||
|
||||
|
@ -50,7 +49,8 @@ class P2pMediaLoaderPlugin extends Plugin {
|
|||
return
|
||||
}
|
||||
|
||||
videojs.Html5Hlsjs.addHook('beforeinitialize', (videojsPlayer: any, hlsjs: any) => {
|
||||
// FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080
|
||||
(videojs as any).Html5Hlsjs.addHook('beforeinitialize', (videojsPlayer: any, hlsjs: any) => {
|
||||
this.hlsjs = hlsjs
|
||||
})
|
||||
|
||||
|
@ -84,8 +84,9 @@ class P2pMediaLoaderPlugin extends Plugin {
|
|||
private initialize () {
|
||||
initHlsJsPlayer(this.hlsjs)
|
||||
|
||||
const tech = this.player.tech_
|
||||
this.p2pEngine = tech.options_.hlsjsConfig.loader.getEngine()
|
||||
// FIXME: typings
|
||||
const options = this.player.tech(true).options_ as any
|
||||
this.p2pEngine = options.hlsjsConfig.loader.getEngine()
|
||||
|
||||
// Avoid using constants to not import hls.hs
|
||||
// https://github.com/video-dev/hls.js/blob/master/src/events.js#L37
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
import { VideoFile } from '../../../../shared/models/videos'
|
||||
// @ts-ignore
|
||||
import * as videojs from 'video.js'
|
||||
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js'
|
||||
import 'videojs-hotkeys'
|
||||
import 'videojs-dock'
|
||||
import 'videojs-contextmenu-ui'
|
||||
import 'videojs-contrib-quality-levels'
|
||||
import './upnext/end-card'
|
||||
import './upnext/upnext-plugin'
|
||||
import './bezels/bezels-plugin'
|
||||
import './peertube-plugin'
|
||||
import './videojs-components/next-video-button'
|
||||
import './videojs-components/peertube-link-button'
|
||||
import './videojs-components/resolution-menu-button'
|
||||
import './videojs-components/settings-menu-button'
|
||||
import './videojs-components/p2p-info-button'
|
||||
import './videojs-components/peertube-link-button'
|
||||
import './videojs-components/peertube-load-progress-bar'
|
||||
import './videojs-components/resolution-menu-button'
|
||||
import './videojs-components/resolution-menu-item'
|
||||
import './videojs-components/settings-dialog'
|
||||
import './videojs-components/settings-menu-button'
|
||||
import './videojs-components/settings-menu-item'
|
||||
import './videojs-components/settings-panel'
|
||||
import './videojs-components/settings-panel-child'
|
||||
import './videojs-components/theater-button'
|
||||
import { P2PMediaLoaderPluginOptions, UserWatching, VideoJSCaption, VideoJSPluginOptions, videojsUntyped } from './peertube-videojs-typings'
|
||||
import { P2PMediaLoaderPluginOptions, UserWatching, VideoJSCaption, VideoJSPluginOptions } from './peertube-videojs-typings'
|
||||
import { buildVideoEmbed, buildVideoLink, copyToClipboard, getRtcConfig } from './utils'
|
||||
import { isDefaultLocale } from '../../../../shared/models/i18n/i18n'
|
||||
import { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
|
||||
|
@ -24,12 +29,17 @@ import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
|
|||
import { getStoredP2PEnabled } from './peertube-player-local-storage'
|
||||
import { TranslationsManager } from './translations-manager'
|
||||
|
||||
// For VideoJS
|
||||
(window as any).WebVTT = require('vtt.js/lib/vtt.js').WebVTT;
|
||||
|
||||
// Change 'Playback Rate' to 'Speed' (smaller for our settings menu)
|
||||
videojsUntyped.getComponent('PlaybackRateMenuButton').prototype.controlText_ = 'Speed'
|
||||
(videojs.getComponent('PlaybackRateMenuButton') as any).prototype.controlText_ = 'Speed'
|
||||
|
||||
const CaptionsButton = videojs.getComponent('CaptionsButton') as any
|
||||
// Change Captions to Subtitles/CC
|
||||
videojsUntyped.getComponent('CaptionsButton').prototype.controlText_ = 'Subtitles/CC'
|
||||
CaptionsButton.prototype.controlText_ = 'Subtitles/CC'
|
||||
// We just want to display 'Off' instead of 'captions off', keep a space so the variable == true (hacky I know)
|
||||
videojsUntyped.getComponent('CaptionsButton').prototype.label_ = ' '
|
||||
CaptionsButton.prototype.label_ = ' '
|
||||
|
||||
export type PlayerMode = 'webtorrent' | 'p2p-media-loader'
|
||||
|
||||
|
@ -92,9 +102,9 @@ export type PeertubePlayerManagerOptions = {
|
|||
|
||||
export class PeertubePlayerManager {
|
||||
private static playerElementClassName: string
|
||||
private static onPlayerChange: (player: any) => void
|
||||
private static onPlayerChange: (player: VideoJsPlayer) => void
|
||||
|
||||
static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions, onPlayerChange: (player: any) => void) {
|
||||
static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions, onPlayerChange: (player: VideoJsPlayer) => void) {
|
||||
let p2pMediaLoader: any
|
||||
|
||||
this.onPlayerChange = onPlayerChange
|
||||
|
@ -114,12 +124,12 @@ export class PeertubePlayerManager {
|
|||
|
||||
const self = this
|
||||
return new Promise(res => {
|
||||
videojs(options.common.playerElement, videojsOptions, function (this: any) {
|
||||
videojs(options.common.playerElement, videojsOptions, function (this: VideoJsPlayer) {
|
||||
const player = this
|
||||
|
||||
let alreadyFallback = false
|
||||
|
||||
player.tech_.one('error', () => {
|
||||
player.tech(true).one('error', () => {
|
||||
if (!alreadyFallback) self.maybeFallbackToWebTorrent(mode, player, options)
|
||||
alreadyFallback = true
|
||||
})
|
||||
|
@ -164,7 +174,7 @@ export class PeertubePlayerManager {
|
|||
const videojsOptions = this.getVideojsOptions(mode, options)
|
||||
|
||||
const self = this
|
||||
videojs(newVideoElement, videojsOptions, function (this: any) {
|
||||
videojs(newVideoElement, videojsOptions, function (this: VideoJsPlayer) {
|
||||
const player = this
|
||||
|
||||
self.addContextMenu(mode, player, options.common.embedUrl)
|
||||
|
@ -173,7 +183,11 @@ export class PeertubePlayerManager {
|
|||
})
|
||||
}
|
||||
|
||||
private static getVideojsOptions (mode: PlayerMode, options: PeertubePlayerManagerOptions, p2pMediaLoaderModule?: any) {
|
||||
private static getVideojsOptions (
|
||||
mode: PlayerMode,
|
||||
options: PeertubePlayerManagerOptions,
|
||||
p2pMediaLoaderModule?: any
|
||||
): VideoJsPlayerOptions {
|
||||
const commonOptions = options.common
|
||||
|
||||
let autoplay = commonOptions.autoplay
|
||||
|
@ -213,7 +227,7 @@ export class PeertubePlayerManager {
|
|||
html5,
|
||||
|
||||
// We don't use text track settings for now
|
||||
textTrackSettings: false,
|
||||
textTrackSettings: false as any, // FIXME: typings
|
||||
controls: commonOptions.controls !== undefined ? commonOptions.controls : true,
|
||||
loop: commonOptions.loop !== undefined ? commonOptions.loop : false,
|
||||
|
||||
|
@ -237,7 +251,7 @@ export class PeertubePlayerManager {
|
|||
peertubeLink: commonOptions.peertubeLink,
|
||||
theaterButton: commonOptions.theaterButton,
|
||||
nextVideo: commonOptions.nextVideo
|
||||
})
|
||||
}) as any // FIXME: typings
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -406,7 +420,7 @@ export class PeertubePlayerManager {
|
|||
return children
|
||||
}
|
||||
|
||||
private static addContextMenu (mode: PlayerMode, player: any, videoEmbedUrl: string) {
|
||||
private static addContextMenu (mode: PlayerMode, player: VideoJsPlayer, videoEmbedUrl: string) {
|
||||
const content = [
|
||||
{
|
||||
label: player.localize('Copy the video URL'),
|
||||
|
@ -416,9 +430,8 @@ export class PeertubePlayerManager {
|
|||
},
|
||||
{
|
||||
label: player.localize('Copy the video URL at the current time'),
|
||||
listener: function () {
|
||||
const player = this as videojs.Player
|
||||
copyToClipboard(buildVideoLink({ startTime: player.currentTime() }))
|
||||
listener: function (this: VideoJsPlayer) {
|
||||
copyToClipboard(buildVideoLink({ startTime: this.currentTime() }))
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -432,9 +445,8 @@ export class PeertubePlayerManager {
|
|||
if (mode === 'webtorrent') {
|
||||
content.push({
|
||||
label: player.localize('Copy magnet URI'),
|
||||
listener: function () {
|
||||
const player = this as videojs.Player
|
||||
copyToClipboard(player.webtorrent().getCurrentVideoFile().magnetUri)
|
||||
listener: function (this: VideoJsPlayer) {
|
||||
copyToClipboard(this.webtorrent().getCurrentVideoFile().magnetUri)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -472,7 +484,8 @@ export class PeertubePlayerManager {
|
|||
return event.key === '>'
|
||||
},
|
||||
handler: function (player: videojs.Player) {
|
||||
player.playbackRate((player.playbackRate() + 0.1).toFixed(2))
|
||||
const newValue = Math.min(player.playbackRate() + 0.1, 5)
|
||||
player.playbackRate(parseFloat(newValue.toFixed(2)))
|
||||
}
|
||||
},
|
||||
decreasePlaybackRateKey: {
|
||||
|
@ -480,7 +493,8 @@ export class PeertubePlayerManager {
|
|||
return event.key === '<'
|
||||
},
|
||||
handler: function (player: videojs.Player) {
|
||||
player.playbackRate((player.playbackRate() - 0.1).toFixed(2))
|
||||
const newValue = Math.max(player.playbackRate() - 0.1, 0.10)
|
||||
player.playbackRate(parseFloat(newValue.toFixed(2)))
|
||||
}
|
||||
},
|
||||
frameByFrame: {
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
// FIXME: something weird with our path definition in tsconfig and typings
|
||||
// @ts-ignore
|
||||
import * as videojs from 'video.js'
|
||||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
import './videojs-components/settings-menu-button'
|
||||
import {
|
||||
PeerTubePluginOptions,
|
||||
ResolutionUpdateData,
|
||||
UserWatching,
|
||||
VideoJSCaption,
|
||||
VideoJSComponentInterface,
|
||||
videojsUntyped
|
||||
VideoJSCaption
|
||||
} from './peertube-videojs-typings'
|
||||
import { isMobile, timeToInt } from './utils'
|
||||
import {
|
||||
|
@ -20,7 +16,8 @@ import {
|
|||
saveVolumeInStore
|
||||
} from './peertube-player-local-storage'
|
||||
|
||||
const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin')
|
||||
const Plugin = videojs.getPlugin('plugin')
|
||||
|
||||
class PeerTubePlugin extends Plugin {
|
||||
private readonly videoViewUrl: string
|
||||
private readonly videoDuration: number
|
||||
|
@ -28,7 +25,6 @@ class PeerTubePlugin extends Plugin {
|
|||
USER_WATCHING_VIDEO_INTERVAL: 5000 // Every 5 seconds, notify the user is watching the video
|
||||
}
|
||||
|
||||
private player: any
|
||||
private videoCaptions: VideoJSCaption[]
|
||||
private defaultSubtitle: string
|
||||
|
||||
|
@ -40,8 +36,8 @@ class PeerTubePlugin extends Plugin {
|
|||
private mouseInControlBar = false
|
||||
private readonly savedInactivityTimeout: number
|
||||
|
||||
constructor (player: videojs.Player, options: PeerTubePluginOptions) {
|
||||
super(player, options)
|
||||
constructor (player: VideoJsPlayer, options?: PeerTubePluginOptions) {
|
||||
super(player)
|
||||
|
||||
this.videoViewUrl = options.videoViewUrl
|
||||
this.videoDuration = options.videoDuration
|
||||
|
@ -67,7 +63,7 @@ class PeerTubePlugin extends Plugin {
|
|||
this.player.p2pMediaLoader().on('resolutionChange', (_: any, d: any) => this.handleResolutionChange(d))
|
||||
}
|
||||
|
||||
this.player.tech_.on('loadedqualitydata', () => {
|
||||
this.player.tech(true).on('loadedqualitydata', () => {
|
||||
setTimeout(() => {
|
||||
// Replay a resolution change, now we loaded all quality data
|
||||
if (this.lastResolutionChange) this.handleResolutionChange(this.lastResolutionChange)
|
||||
|
@ -102,7 +98,7 @@ class PeerTubePlugin extends Plugin {
|
|||
}
|
||||
|
||||
this.player.textTracks().on('change', () => {
|
||||
const showing = this.player.textTracks().tracks_.find((t: { kind: string, mode: string }) => {
|
||||
const showing = this.player.textTracks().tracks_.find(t => {
|
||||
return t.kind === 'captions' && t.mode === 'showing'
|
||||
})
|
||||
|
||||
|
@ -262,7 +258,7 @@ class PeerTubePlugin extends Plugin {
|
|||
|
||||
// Thanks: https://github.com/videojs/video.js/issues/4460#issuecomment-312861657
|
||||
private initSmoothProgressBar () {
|
||||
const SeekBar = videojsUntyped.getComponent('SeekBar')
|
||||
const SeekBar = videojs.getComponent('SeekBar') as any
|
||||
SeekBar.prototype.getPercent = function getPercent () {
|
||||
// Allows for smooth scrubbing, when player can't keep up.
|
||||
// const time = (this.player_.scrubbing()) ?
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
// FIXME: something weird with our path definition in tsconfig and typings
|
||||
// @ts-ignore
|
||||
import * as videojs from 'video.js'
|
||||
|
||||
import videojs from 'video.js'
|
||||
import { PeerTubePlugin } from './peertube-plugin'
|
||||
import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin'
|
||||
import { P2pMediaLoaderPlugin } from './p2p-media-loader/p2p-media-loader-plugin'
|
||||
|
@ -9,22 +6,46 @@ import { PlayerMode } from './peertube-player-manager'
|
|||
import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
|
||||
import { VideoFile } from '@shared/models'
|
||||
|
||||
declare namespace videojs {
|
||||
interface Player {
|
||||
declare module 'video.js' {
|
||||
export interface VideoJsPlayer {
|
||||
theaterEnabled: boolean
|
||||
|
||||
// FIXME: add it to upstream typings
|
||||
posterImage: {
|
||||
show (): void
|
||||
hide (): void
|
||||
}
|
||||
|
||||
handleTechSeeked_ (): void
|
||||
|
||||
// Plugins
|
||||
|
||||
peertube (): PeerTubePlugin
|
||||
webtorrent (): WebTorrentPlugin
|
||||
p2pMediaLoader (): P2pMediaLoaderPlugin
|
||||
|
||||
contextmenuUI (options: any): any
|
||||
|
||||
bezels (): void
|
||||
|
||||
qualityLevels (): { height: number, id: number }[] & {
|
||||
selectedIndex: number
|
||||
|
||||
addQualityLevel (representation: {
|
||||
id: number
|
||||
label: string
|
||||
height: number,
|
||||
_enabled: boolean
|
||||
}): void
|
||||
}
|
||||
|
||||
textTracks (): TextTrackList & {
|
||||
on: Function
|
||||
tracks_: { kind: string, mode: string, language: string }[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface VideoJSComponentInterface {
|
||||
_player: videojs.Player
|
||||
|
||||
new (player: videojs.Player, options?: any): any
|
||||
|
||||
registerComponent (name: string, obj: any): any
|
||||
}
|
||||
|
||||
type VideoJSCaption = {
|
||||
label: string
|
||||
language: string
|
||||
|
@ -78,9 +99,6 @@ type VideoJSPluginOptions = {
|
|||
p2pMediaLoader?: P2PMediaLoaderPluginOptions
|
||||
}
|
||||
|
||||
// videojs typings don't have some method we need
|
||||
const videojsUntyped = videojs as any
|
||||
|
||||
type LoadedQualityData = {
|
||||
qualitySwitchCallback: Function,
|
||||
qualityData: {
|
||||
|
@ -123,8 +141,6 @@ export {
|
|||
PlayerNetworkInfo,
|
||||
ResolutionUpdateData,
|
||||
AutoResolutionUpdateData,
|
||||
VideoJSComponentInterface,
|
||||
videojsUntyped,
|
||||
VideoJSCaption,
|
||||
UserWatching,
|
||||
PeerTubePluginOptions,
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
|
||||
function getMainTemplate (options: any) {
|
||||
return `
|
||||
<div class="vjs-upnext-top">
|
||||
<span class="vjs-upnext-headtext">${options.headText}</span>
|
||||
<div class="vjs-upnext-title"></div>
|
||||
</div>
|
||||
<div class="vjs-upnext-autoplay-icon">
|
||||
<svg height="100%" version="1.1" viewbox="0 0 98 98" width="100%">
|
||||
<circle class="vjs-upnext-svg-autoplay-circle" cx="49" cy="49" fill="#000" fill-opacity="0.8" r="48"></circle>
|
||||
<circle class="vjs-upnext-svg-autoplay-ring" cx="-49" cy="49" fill-opacity="0" r="46.5" stroke="#FFFFFF" stroke-width="4" transform="rotate(-90)"></circle>
|
||||
<polygon class="vjs-upnext-svg-autoplay-triangle" fill="#fff" points="32,27 72,49 32,71"></polygon></svg>
|
||||
</div>
|
||||
<span class="vjs-upnext-bottom">
|
||||
<span class="vjs-upnext-cancel">
|
||||
<button class="vjs-upnext-cancel-button" tabindex="0" aria-label="Cancel autoplay">${options.cancelText}</button>
|
||||
</span>
|
||||
<span class="vjs-upnext-suspended">${options.suspendedText}</span>
|
||||
</span>
|
||||
`
|
||||
}
|
||||
|
||||
export interface EndCardOptions extends videojs.ComponentOptions {
|
||||
next: Function,
|
||||
getTitle: () => string
|
||||
timeout: number
|
||||
cancelText: string
|
||||
headText: string
|
||||
suspendedText: string
|
||||
condition: () => boolean
|
||||
suspended: () => boolean
|
||||
}
|
||||
|
||||
const Component = videojs.getComponent('Component')
|
||||
class EndCard extends Component {
|
||||
options_: EndCardOptions
|
||||
|
||||
dashOffsetTotal = 586
|
||||
dashOffsetStart = 293
|
||||
interval = 50
|
||||
upNextEvents = new videojs.EventTarget()
|
||||
ticks = 0
|
||||
totalTicks: number
|
||||
|
||||
container: HTMLDivElement
|
||||
title: HTMLElement
|
||||
autoplayRing: HTMLElement
|
||||
cancelButton: HTMLElement
|
||||
suspendedMessage: HTMLElement
|
||||
nextButton: HTMLElement
|
||||
|
||||
constructor (player: VideoJsPlayer, options: EndCardOptions) {
|
||||
super(player, options)
|
||||
|
||||
this.totalTicks = this.options_.timeout / this.interval
|
||||
|
||||
player.on('ended', (_: any) => {
|
||||
if (!this.options_.condition()) return
|
||||
|
||||
player.addClass('vjs-upnext--showing')
|
||||
this.showCard((canceled: boolean) => {
|
||||
player.removeClass('vjs-upnext--showing')
|
||||
this.container.style.display = 'none'
|
||||
if (!canceled) {
|
||||
this.options_.next()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
player.on('playing', () => {
|
||||
this.upNextEvents.trigger('playing')
|
||||
})
|
||||
}
|
||||
|
||||
createEl () {
|
||||
const container = super.createEl('div', {
|
||||
className: 'vjs-upnext-content',
|
||||
innerHTML: getMainTemplate(this.options_)
|
||||
}) as HTMLDivElement
|
||||
|
||||
this.container = container
|
||||
container.style.display = 'none'
|
||||
|
||||
this.autoplayRing = container.getElementsByClassName('vjs-upnext-svg-autoplay-ring')[0] as HTMLElement
|
||||
this.title = container.getElementsByClassName('vjs-upnext-title')[0] as HTMLElement
|
||||
this.cancelButton = container.getElementsByClassName('vjs-upnext-cancel-button')[0] as HTMLElement
|
||||
this.suspendedMessage = container.getElementsByClassName('vjs-upnext-suspended')[0] as HTMLElement
|
||||
this.nextButton = container.getElementsByClassName('vjs-upnext-autoplay-icon')[0] as HTMLElement
|
||||
|
||||
this.cancelButton.onclick = () => {
|
||||
this.upNextEvents.trigger('cancel')
|
||||
}
|
||||
|
||||
this.nextButton.onclick = () => {
|
||||
this.upNextEvents.trigger('next')
|
||||
}
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
showCard (cb: Function) {
|
||||
let timeout: any
|
||||
|
||||
this.autoplayRing.setAttribute('stroke-dasharray', '' + this.dashOffsetStart)
|
||||
this.autoplayRing.setAttribute('stroke-dashoffset', '' + -this.dashOffsetStart)
|
||||
|
||||
this.title.innerHTML = this.options_.getTitle()
|
||||
|
||||
this.upNextEvents.one('cancel', () => {
|
||||
clearTimeout(timeout)
|
||||
cb(true)
|
||||
})
|
||||
|
||||
this.upNextEvents.one('playing', () => {
|
||||
clearTimeout(timeout)
|
||||
cb(true)
|
||||
})
|
||||
|
||||
this.upNextEvents.one('next', () => {
|
||||
clearTimeout(timeout)
|
||||
cb(false)
|
||||
})
|
||||
|
||||
const goToPercent = (percent: number) => {
|
||||
const newOffset = Math.max(-this.dashOffsetTotal, - this.dashOffsetStart - percent * this.dashOffsetTotal / 2 / 100)
|
||||
this.autoplayRing.setAttribute('stroke-dashoffset', '' + newOffset)
|
||||
}
|
||||
|
||||
const tick = () => {
|
||||
goToPercent((this.ticks++) * 100 / this.totalTicks)
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
if (this.options_.suspended()) {
|
||||
this.suspendedMessage.innerText = this.options_.suspendedText
|
||||
goToPercent(0)
|
||||
this.ticks = 0
|
||||
timeout = setTimeout(update.bind(this), 300) // checks once supsended can be a bit longer
|
||||
} else if (this.ticks >= this.totalTicks) {
|
||||
clearTimeout(timeout)
|
||||
cb(false)
|
||||
} else {
|
||||
this.suspendedMessage.innerText = ''
|
||||
tick()
|
||||
timeout = setTimeout(update.bind(this), this.interval)
|
||||
}
|
||||
}
|
||||
|
||||
this.container.style.display = 'block'
|
||||
timeout = setTimeout(update.bind(this), this.interval)
|
||||
}
|
||||
}
|
||||
|
||||
videojs.registerComponent('EndCard', EndCard)
|
|
@ -1,154 +1,11 @@
|
|||
// @ts-ignore
|
||||
import * as videojs from 'video.js'
|
||||
import { VideoJSComponentInterface } from '../peertube-videojs-typings'
|
||||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
import { EndCardOptions } from './end-card'
|
||||
|
||||
function getMainTemplate (options: any) {
|
||||
return `
|
||||
<div class="vjs-upnext-top">
|
||||
<span class="vjs-upnext-headtext">${options.headText}</span>
|
||||
<div class="vjs-upnext-title"></div>
|
||||
</div>
|
||||
<div class="vjs-upnext-autoplay-icon">
|
||||
<svg height="100%" version="1.1" viewbox="0 0 98 98" width="100%">
|
||||
<circle class="vjs-upnext-svg-autoplay-circle" cx="49" cy="49" fill="#000" fill-opacity="0.8" r="48"></circle>
|
||||
<circle class="vjs-upnext-svg-autoplay-ring" cx="-49" cy="49" fill-opacity="0" r="46.5" stroke="#FFFFFF" stroke-width="4" transform="rotate(-90)"></circle>
|
||||
<polygon class="vjs-upnext-svg-autoplay-triangle" fill="#fff" points="32,27 72,49 32,71"></polygon></svg>
|
||||
</div>
|
||||
<span class="vjs-upnext-bottom">
|
||||
<span class="vjs-upnext-cancel">
|
||||
<button class="vjs-upnext-cancel-button" tabindex="0" aria-label="Cancel autoplay">${options.cancelText}</button>
|
||||
</span>
|
||||
<span class="vjs-upnext-suspended">${options.suspendedText}</span>
|
||||
</span>
|
||||
`
|
||||
}
|
||||
const Plugin = videojs.getPlugin('plugin')
|
||||
|
||||
// @ts-ignore-start
|
||||
const Component = videojs.getComponent('Component')
|
||||
class EndCard extends Component {
|
||||
options_: any
|
||||
dashOffsetTotal = 586
|
||||
dashOffsetStart = 293
|
||||
interval = 50
|
||||
upNextEvents = new videojs.EventTarget()
|
||||
ticks = 0
|
||||
totalTicks: number
|
||||
|
||||
container: HTMLElement
|
||||
title: HTMLElement
|
||||
autoplayRing: HTMLElement
|
||||
cancelButton: HTMLElement
|
||||
suspendedMessage: HTMLElement
|
||||
nextButton: HTMLElement
|
||||
|
||||
constructor (player: videojs.Player, options: any) {
|
||||
super(player, options)
|
||||
|
||||
this.totalTicks = this.options_.timeout / this.interval
|
||||
|
||||
player.on('ended', (_: any) => {
|
||||
if (!this.options_.condition()) return
|
||||
|
||||
player.addClass('vjs-upnext--showing')
|
||||
this.showCard((canceled: boolean) => {
|
||||
player.removeClass('vjs-upnext--showing')
|
||||
this.container.style.display = 'none'
|
||||
if (!canceled) {
|
||||
this.options_.next()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
player.on('playing', () => {
|
||||
this.upNextEvents.trigger('playing')
|
||||
})
|
||||
}
|
||||
|
||||
createEl () {
|
||||
const container = super.createEl('div', {
|
||||
className: 'vjs-upnext-content',
|
||||
innerHTML: getMainTemplate(this.options_)
|
||||
})
|
||||
|
||||
this.container = container
|
||||
container.style.display = 'none'
|
||||
|
||||
this.autoplayRing = container.getElementsByClassName('vjs-upnext-svg-autoplay-ring')[0]
|
||||
this.title = container.getElementsByClassName('vjs-upnext-title')[0]
|
||||
this.cancelButton = container.getElementsByClassName('vjs-upnext-cancel-button')[0]
|
||||
this.suspendedMessage = container.getElementsByClassName('vjs-upnext-suspended')[0]
|
||||
this.nextButton = container.getElementsByClassName('vjs-upnext-autoplay-icon')[0]
|
||||
|
||||
this.cancelButton.onclick = () => {
|
||||
this.upNextEvents.trigger('cancel')
|
||||
}
|
||||
|
||||
this.nextButton.onclick = () => {
|
||||
this.upNextEvents.trigger('next')
|
||||
}
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
showCard (cb: Function) {
|
||||
let timeout: any
|
||||
|
||||
this.autoplayRing.setAttribute('stroke-dasharray', '' + this.dashOffsetStart)
|
||||
this.autoplayRing.setAttribute('stroke-dashoffset', '' + -this.dashOffsetStart)
|
||||
|
||||
this.title.innerHTML = this.options_.getTitle()
|
||||
|
||||
this.upNextEvents.one('cancel', () => {
|
||||
clearTimeout(timeout)
|
||||
cb(true)
|
||||
})
|
||||
|
||||
this.upNextEvents.one('playing', () => {
|
||||
clearTimeout(timeout)
|
||||
cb(true)
|
||||
})
|
||||
|
||||
this.upNextEvents.one('next', () => {
|
||||
clearTimeout(timeout)
|
||||
cb(false)
|
||||
})
|
||||
|
||||
const goToPercent = (percent: number) => {
|
||||
const newOffset = Math.max(-this.dashOffsetTotal, - this.dashOffsetStart - percent * this.dashOffsetTotal / 2 / 100)
|
||||
this.autoplayRing.setAttribute('stroke-dashoffset', '' + newOffset)
|
||||
}
|
||||
|
||||
const tick = () => {
|
||||
goToPercent((this.ticks++) * 100 / this.totalTicks)
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
if (this.options_.suspended()) {
|
||||
this.suspendedMessage.innerText = this.options_.suspendedText
|
||||
goToPercent(0)
|
||||
this.ticks = 0
|
||||
timeout = setTimeout(update.bind(this), 300) // checks once supsended can be a bit longer
|
||||
} else if (this.ticks >= this.totalTicks) {
|
||||
clearTimeout(timeout)
|
||||
cb(false)
|
||||
} else {
|
||||
this.suspendedMessage.innerText = ''
|
||||
tick()
|
||||
timeout = setTimeout(update.bind(this), this.interval)
|
||||
}
|
||||
}
|
||||
|
||||
this.container.style.display = 'block'
|
||||
timeout = setTimeout(update.bind(this), this.interval)
|
||||
}
|
||||
}
|
||||
// @ts-ignore-end
|
||||
|
||||
videojs.registerComponent('EndCard', EndCard)
|
||||
|
||||
const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin')
|
||||
class UpNextPlugin extends Plugin {
|
||||
constructor (player: videojs.Player, options: any = {}) {
|
||||
|
||||
constructor (player: VideoJsPlayer, options: Partial<EndCardOptions> = {}) {
|
||||
const settings = {
|
||||
next: options.next,
|
||||
getTitle: options.getTitle,
|
||||
|
@ -160,7 +17,7 @@ class UpNextPlugin extends Plugin {
|
|||
suspended: options.suspended
|
||||
}
|
||||
|
||||
super(player, settings)
|
||||
super(player)
|
||||
|
||||
this.player.ready(() => {
|
||||
player.addClass('vjs-upnext')
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
|
||||
// FIXME: something weird with our path definition in tsconfig and typings
|
||||
// @ts-ignore
|
||||
import { Player } from 'video.js'
|
||||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
|
||||
const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button')
|
||||
const Button = videojs.getComponent('Button')
|
||||
|
||||
export interface NextVideoButtonOptions extends videojs.ComponentOptions {
|
||||
handler: Function
|
||||
}
|
||||
|
||||
class NextVideoButton extends Button {
|
||||
private readonly nextVideoButtonOptions: NextVideoButtonOptions
|
||||
|
||||
constructor (player: Player, options: any) {
|
||||
constructor (player: VideoJsPlayer, options?: NextVideoButtonOptions) {
|
||||
super(player, options)
|
||||
|
||||
this.nextVideoButtonOptions = options
|
||||
}
|
||||
|
||||
createEl () {
|
||||
const button = videojsUntyped.dom.createEl('button', {
|
||||
const button = videojs.dom.createEl('button', {
|
||||
className: 'vjs-next-video'
|
||||
})
|
||||
const nextIcon = videojsUntyped.dom.createEl('span', {
|
||||
}) as HTMLButtonElement
|
||||
const nextIcon = videojs.dom.createEl('span', {
|
||||
className: 'icon icon-next'
|
||||
})
|
||||
button.appendChild(nextIcon)
|
||||
|
@ -26,11 +30,8 @@ class NextVideoButton extends Button {
|
|||
}
|
||||
|
||||
handleClick () {
|
||||
this.options_.handler()
|
||||
this.nextVideoButtonOptions.handler()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
NextVideoButton.prototype.controlText_ = 'Next video'
|
||||
|
||||
NextVideoButton.registerComponent('NextVideoButton', NextVideoButton)
|
||||
videojs.registerComponent('NextVideoButton', NextVideoButton)
|
||||
|
|
|
@ -1,63 +1,64 @@
|
|||
import { PlayerNetworkInfo, VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
|
||||
import { PlayerNetworkInfo } from '../peertube-videojs-typings'
|
||||
import videojs from 'video.js'
|
||||
import { bytes } from '../utils'
|
||||
|
||||
const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button')
|
||||
const Button = videojs.getComponent('Button')
|
||||
class P2pInfoButton extends Button {
|
||||
|
||||
createEl () {
|
||||
const div = videojsUntyped.dom.createEl('div', {
|
||||
const div = videojs.dom.createEl('div', {
|
||||
className: 'vjs-peertube'
|
||||
})
|
||||
const subDivWebtorrent = videojsUntyped.dom.createEl('div', {
|
||||
const subDivWebtorrent = videojs.dom.createEl('div', {
|
||||
className: 'vjs-peertube-hidden' // Hide the stats before we get the info
|
||||
})
|
||||
}) as HTMLDivElement
|
||||
div.appendChild(subDivWebtorrent)
|
||||
|
||||
const downloadIcon = videojsUntyped.dom.createEl('span', {
|
||||
const downloadIcon = videojs.dom.createEl('span', {
|
||||
className: 'icon icon-download'
|
||||
})
|
||||
subDivWebtorrent.appendChild(downloadIcon)
|
||||
|
||||
const downloadSpeedText = videojsUntyped.dom.createEl('span', {
|
||||
const downloadSpeedText = videojs.dom.createEl('span', {
|
||||
className: 'download-speed-text'
|
||||
})
|
||||
const downloadSpeedNumber = videojsUntyped.dom.createEl('span', {
|
||||
const downloadSpeedNumber = videojs.dom.createEl('span', {
|
||||
className: 'download-speed-number'
|
||||
})
|
||||
const downloadSpeedUnit = videojsUntyped.dom.createEl('span')
|
||||
const downloadSpeedUnit = videojs.dom.createEl('span')
|
||||
downloadSpeedText.appendChild(downloadSpeedNumber)
|
||||
downloadSpeedText.appendChild(downloadSpeedUnit)
|
||||
subDivWebtorrent.appendChild(downloadSpeedText)
|
||||
|
||||
const uploadIcon = videojsUntyped.dom.createEl('span', {
|
||||
const uploadIcon = videojs.dom.createEl('span', {
|
||||
className: 'icon icon-upload'
|
||||
})
|
||||
subDivWebtorrent.appendChild(uploadIcon)
|
||||
|
||||
const uploadSpeedText = videojsUntyped.dom.createEl('span', {
|
||||
const uploadSpeedText = videojs.dom.createEl('span', {
|
||||
className: 'upload-speed-text'
|
||||
})
|
||||
const uploadSpeedNumber = videojsUntyped.dom.createEl('span', {
|
||||
const uploadSpeedNumber = videojs.dom.createEl('span', {
|
||||
className: 'upload-speed-number'
|
||||
})
|
||||
const uploadSpeedUnit = videojsUntyped.dom.createEl('span')
|
||||
const uploadSpeedUnit = videojs.dom.createEl('span')
|
||||
uploadSpeedText.appendChild(uploadSpeedNumber)
|
||||
uploadSpeedText.appendChild(uploadSpeedUnit)
|
||||
subDivWebtorrent.appendChild(uploadSpeedText)
|
||||
|
||||
const peersText = videojsUntyped.dom.createEl('span', {
|
||||
const peersText = videojs.dom.createEl('span', {
|
||||
className: 'peers-text'
|
||||
})
|
||||
const peersNumber = videojsUntyped.dom.createEl('span', {
|
||||
const peersNumber = videojs.dom.createEl('span', {
|
||||
className: 'peers-number'
|
||||
})
|
||||
subDivWebtorrent.appendChild(peersNumber)
|
||||
subDivWebtorrent.appendChild(peersText)
|
||||
|
||||
const subDivHttp = videojsUntyped.dom.createEl('div', {
|
||||
const subDivHttp = videojs.dom.createEl('div', {
|
||||
className: 'vjs-peertube-hidden'
|
||||
})
|
||||
const subDivHttpText = videojsUntyped.dom.createEl('span', {
|
||||
const subDivHttpText = videojs.dom.createEl('span', {
|
||||
className: 'http-fallback',
|
||||
textContent: 'HTTP'
|
||||
})
|
||||
|
@ -83,8 +84,8 @@ class P2pInfoButton extends Button {
|
|||
const totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded)
|
||||
const numPeers = p2pStats.numPeers
|
||||
|
||||
subDivWebtorrent.title = this.player_.localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n' +
|
||||
this.player_.localize('Total uploaded: ' + totalUploaded.join(' '))
|
||||
subDivWebtorrent.title = this.player().localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n' +
|
||||
this.player().localize('Total uploaded: ' + totalUploaded.join(' '))
|
||||
|
||||
downloadSpeedNumber.textContent = downloadSpeed[ 0 ]
|
||||
downloadSpeedUnit.textContent = ' ' + downloadSpeed[ 1 ]
|
||||
|
@ -92,14 +93,15 @@ class P2pInfoButton extends Button {
|
|||
uploadSpeedNumber.textContent = uploadSpeed[ 0 ]
|
||||
uploadSpeedUnit.textContent = ' ' + uploadSpeed[ 1 ]
|
||||
|
||||
peersNumber.textContent = numPeers
|
||||
peersText.textContent = ' ' + (numPeers > 1 ? this.player_.localize('peers') : this.player_.localize('peer'))
|
||||
peersNumber.textContent = numPeers.toString()
|
||||
peersText.textContent = ' ' + (numPeers > 1 ? this.player().localize('peers') : this.player_.localize('peer'))
|
||||
|
||||
subDivHttp.className = 'vjs-peertube-hidden'
|
||||
subDivWebtorrent.className = 'vjs-peertube-displayed'
|
||||
})
|
||||
|
||||
return div
|
||||
return div as HTMLButtonElement
|
||||
}
|
||||
}
|
||||
Button.registerComponent('P2PInfoButton', P2pInfoButton)
|
||||
|
||||
videojs.registerComponent('P2PInfoButton', P2pInfoButton)
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
|
||||
import { buildVideoLink } from '../utils'
|
||||
// FIXME: something weird with our path definition in tsconfig and typings
|
||||
// @ts-ignore
|
||||
import { Player } from 'video.js'
|
||||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
|
||||
const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button')
|
||||
const Button = videojs.getComponent('Button')
|
||||
class PeerTubeLinkButton extends Button {
|
||||
|
||||
constructor (player: Player, options: any) {
|
||||
constructor (player: VideoJsPlayer, options?: videojs.ComponentOptions) {
|
||||
super(player, options)
|
||||
}
|
||||
|
||||
|
@ -20,21 +17,22 @@ class PeerTubeLinkButton extends Button {
|
|||
}
|
||||
|
||||
handleClick () {
|
||||
this.player_.pause()
|
||||
this.player().pause()
|
||||
}
|
||||
|
||||
private buildElement () {
|
||||
const el = videojsUntyped.dom.createEl('a', {
|
||||
const el = videojs.dom.createEl('a', {
|
||||
href: buildVideoLink(),
|
||||
innerHTML: 'PeerTube',
|
||||
title: this.player_.localize('Go to the video page'),
|
||||
title: this.player().localize('Go to the video page'),
|
||||
className: 'vjs-peertube-link',
|
||||
target: '_blank'
|
||||
})
|
||||
|
||||
el.addEventListener('mouseenter', () => this.updateHref())
|
||||
|
||||
return el
|
||||
return el as HTMLButtonElement
|
||||
}
|
||||
}
|
||||
Button.registerComponent('PeerTubeLinkButton', PeerTubeLinkButton)
|
||||
|
||||
videojs.registerComponent('PeerTubeLinkButton', PeerTubeLinkButton)
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
|
||||
// FIXME: something weird with our path definition in tsconfig and typings
|
||||
// @ts-ignore
|
||||
import { Player } from 'video.js'
|
||||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
|
||||
const Component: VideoJSComponentInterface = videojsUntyped.getComponent('Component')
|
||||
const Component = videojs.getComponent('Component')
|
||||
|
||||
class PeerTubeLoadProgressBar extends Component {
|
||||
partEls_: any[]
|
||||
|
||||
constructor (player: Player, options: any) {
|
||||
constructor (player: VideoJsPlayer, options?: videojs.ComponentOptions) {
|
||||
super(player, options)
|
||||
this.partEls_ = []
|
||||
|
||||
this.on(player, 'progress', this.update)
|
||||
}
|
||||
|
||||
|
@ -22,8 +18,6 @@ class PeerTubeLoadProgressBar extends Component {
|
|||
}
|
||||
|
||||
dispose () {
|
||||
this.partEls_ = null
|
||||
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
|
@ -31,7 +25,8 @@ class PeerTubeLoadProgressBar extends Component {
|
|||
const torrent = this.player().webtorrent().getTorrent()
|
||||
if (!torrent) return
|
||||
|
||||
this.el_.style.width = (torrent.progress * 100) + '%'
|
||||
// FIXME: typings
|
||||
(this.el() as HTMLElement).style.width = (torrent.progress * 100) + '%'
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,22 +1,19 @@
|
|||
// FIXME: something weird with our path definition in tsconfig and typings
|
||||
// @ts-ignore
|
||||
import { Player } from 'video.js'
|
||||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
|
||||
import { LoadedQualityData, VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
|
||||
import { LoadedQualityData } from '../peertube-videojs-typings'
|
||||
import { ResolutionMenuItem } from './resolution-menu-item'
|
||||
|
||||
const Menu: VideoJSComponentInterface = videojsUntyped.getComponent('Menu')
|
||||
const MenuButton: VideoJSComponentInterface = videojsUntyped.getComponent('MenuButton')
|
||||
const Menu = videojs.getComponent('Menu')
|
||||
const MenuButton = videojs.getComponent('MenuButton')
|
||||
class ResolutionMenuButton extends MenuButton {
|
||||
label: HTMLElement
|
||||
labelEl_: any
|
||||
player: Player
|
||||
labelEl_: HTMLElement
|
||||
|
||||
constructor (player: Player, options: any) {
|
||||
constructor (player: VideoJsPlayer, options?: videojs.MenuButtonOptions) {
|
||||
super(player, options)
|
||||
this.player = player
|
||||
|
||||
player.tech_.on('loadedqualitydata', (e: any, data: any) => this.buildQualities(data))
|
||||
this.controlText('Quality')
|
||||
|
||||
player.tech(true).on('loadedqualitydata', (e: any, data: any) => this.buildQualities(data))
|
||||
|
||||
player.peertube().on('resolutionChange', () => setTimeout(() => this.trigger('updateLabel'), 0))
|
||||
}
|
||||
|
@ -24,9 +21,9 @@ class ResolutionMenuButton extends MenuButton {
|
|||
createEl () {
|
||||
const el = super.createEl()
|
||||
|
||||
this.labelEl_ = videojsUntyped.dom.createEl('div', {
|
||||
this.labelEl_ = videojs.dom.createEl('div', {
|
||||
className: 'vjs-resolution-value'
|
||||
})
|
||||
}) as HTMLElement
|
||||
|
||||
el.appendChild(this.labelEl_)
|
||||
|
||||
|
@ -55,7 +52,7 @@ class ResolutionMenuButton extends MenuButton {
|
|||
|
||||
for (const child of children) {
|
||||
if (component !== child) {
|
||||
child.selected(false)
|
||||
(child as videojs.MenuItem).selected(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -76,7 +73,7 @@ class ResolutionMenuButton extends MenuButton {
|
|||
if (d.id === -1) continue
|
||||
|
||||
const label = d.id === 0
|
||||
? this.player.localize('Audio-only')
|
||||
? this.player().localize('Audio-only')
|
||||
: d.label
|
||||
|
||||
this.menu.addChild(new ResolutionMenuItem(
|
||||
|
@ -110,6 +107,5 @@ class ResolutionMenuButton extends MenuButton {
|
|||
this.trigger('menuChanged')
|
||||
}
|
||||
}
|
||||
ResolutionMenuButton.prototype.controlText_ = 'Quality'
|
||||
|
||||
MenuButton.registerComponent('ResolutionMenuButton', ResolutionMenuButton)
|
||||
videojs.registerComponent('ResolutionMenuButton', ResolutionMenuButton)
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
// FIXME: something weird with our path definition in tsconfig and typings
|
||||
// @ts-ignore
|
||||
import { Player } from 'video.js'
|
||||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
import { AutoResolutionUpdateData, ResolutionUpdateData } from '../peertube-videojs-typings'
|
||||
|
||||
import { AutoResolutionUpdateData, ResolutionUpdateData, VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
|
||||
const MenuItem = videojs.getComponent('MenuItem')
|
||||
|
||||
export interface ResolutionMenuItemOptions extends videojs.MenuItemOptions {
|
||||
labels?: { [id: number]: string }
|
||||
id: number
|
||||
callback: Function
|
||||
}
|
||||
|
||||
const MenuItem: VideoJSComponentInterface = videojsUntyped.getComponent('MenuItem')
|
||||
class ResolutionMenuItem extends MenuItem {
|
||||
private readonly id: number
|
||||
private readonly resolutionId: number
|
||||
private readonly label: string
|
||||
// Only used for the automatic item
|
||||
private readonly labels: { [id: number]: string }
|
||||
|
@ -15,7 +19,7 @@ class ResolutionMenuItem extends MenuItem {
|
|||
private autoResolutionPossible: boolean
|
||||
private currentResolutionLabel: string
|
||||
|
||||
constructor (player: Player, options: any) {
|
||||
constructor (player: VideoJsPlayer, options?: ResolutionMenuItemOptions) {
|
||||
options.selectable = true
|
||||
|
||||
super(player, options)
|
||||
|
@ -23,40 +27,40 @@ class ResolutionMenuItem extends MenuItem {
|
|||
this.autoResolutionPossible = true
|
||||
this.currentResolutionLabel = ''
|
||||
|
||||
this.resolutionId = options.id
|
||||
this.label = options.label
|
||||
this.labels = options.labels
|
||||
this.id = options.id
|
||||
this.callback = options.callback
|
||||
|
||||
player.peertube().on('resolutionChange', (_: any, data: ResolutionUpdateData) => this.updateSelection(data))
|
||||
|
||||
// We only want to disable the "Auto" item
|
||||
if (this.id === -1) {
|
||||
if (this.resolutionId === -1) {
|
||||
player.peertube().on('autoResolutionChange', (_: any, data: AutoResolutionUpdateData) => this.updateAutoResolution(data))
|
||||
}
|
||||
}
|
||||
|
||||
handleClick (event: any) {
|
||||
// Auto button disabled?
|
||||
if (this.autoResolutionPossible === false && this.id === -1) return
|
||||
if (this.autoResolutionPossible === false && this.resolutionId === -1) return
|
||||
|
||||
super.handleClick(event)
|
||||
|
||||
this.callback(this.id, 'video')
|
||||
this.callback(this.resolutionId, 'video')
|
||||
}
|
||||
|
||||
updateSelection (data: ResolutionUpdateData) {
|
||||
if (this.id === -1) {
|
||||
if (this.resolutionId === -1) {
|
||||
this.currentResolutionLabel = this.labels[data.id]
|
||||
}
|
||||
|
||||
// Automatic resolution only
|
||||
if (data.auto === true) {
|
||||
this.selected(this.id === -1)
|
||||
this.selected(this.resolutionId === -1)
|
||||
return
|
||||
}
|
||||
|
||||
this.selected(this.id === data.id)
|
||||
this.selected(this.resolutionId === data.id)
|
||||
}
|
||||
|
||||
updateAutoResolution (data: AutoResolutionUpdateData) {
|
||||
|
@ -71,13 +75,13 @@ class ResolutionMenuItem extends MenuItem {
|
|||
}
|
||||
|
||||
getLabel () {
|
||||
if (this.id === -1) {
|
||||
if (this.resolutionId === -1) {
|
||||
return this.label + ' <small>' + this.currentResolutionLabel + '</small>'
|
||||
}
|
||||
|
||||
return this.label
|
||||
}
|
||||
}
|
||||
MenuItem.registerComponent('ResolutionMenuItem', ResolutionMenuItem)
|
||||
videojs.registerComponent('ResolutionMenuItem', ResolutionMenuItem)
|
||||
|
||||
export { ResolutionMenuItem }
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
|
||||
const Component = videojs.getComponent('Component')
|
||||
|
||||
class SettingsDialog extends Component {
|
||||
constructor (player: VideoJsPlayer) {
|
||||
super(player)
|
||||
|
||||
this.hide()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the component's DOM element
|
||||
*
|
||||
* @return {Element}
|
||||
* @method createEl
|
||||
*/
|
||||
createEl () {
|
||||
const uniqueId = this.id()
|
||||
const dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId
|
||||
const dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId
|
||||
|
||||
return super.createEl('div', {
|
||||
className: 'vjs-settings-dialog vjs-modal-overlay',
|
||||
innerHTML: '',
|
||||
tabIndex: -1
|
||||
}, {
|
||||
'role': 'dialog',
|
||||
'aria-labelledby': dialogLabelId,
|
||||
'aria-describedby': dialogDescriptionId
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Component.registerComponent('SettingsDialog', SettingsDialog)
|
||||
|
||||
export { SettingsDialog }
|
|
@ -1,43 +1,52 @@
|
|||
// Author: Yanko Shterev
|
||||
// Thanks https://github.com/yshterev/videojs-settings-menu
|
||||
|
||||
// FIXME: something weird with our path definition in tsconfig and typings
|
||||
// @ts-ignore
|
||||
import * as videojs from 'video.js'
|
||||
|
||||
// Thanks to Yanko Shterev: https://github.com/yshterev/videojs-settings-menu
|
||||
import { SettingsMenuItem } from './settings-menu-item'
|
||||
import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
|
||||
import { toTitleCase } from '../utils'
|
||||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
|
||||
const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button')
|
||||
const Menu: VideoJSComponentInterface = videojsUntyped.getComponent('Menu')
|
||||
const Component: VideoJSComponentInterface = videojsUntyped.getComponent('Component')
|
||||
import { SettingsDialog } from './settings-dialog'
|
||||
import { SettingsPanel } from './settings-panel'
|
||||
import { SettingsPanelChild } from './settings-panel-child'
|
||||
|
||||
const Button = videojs.getComponent('Button')
|
||||
const Menu = videojs.getComponent('Menu')
|
||||
const Component = videojs.getComponent('Component')
|
||||
|
||||
export interface SettingsButtonOptions extends videojs.ComponentOptions {
|
||||
entries: any[]
|
||||
setup?: {
|
||||
maxHeightOffset: number
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsButton extends Button {
|
||||
playerComponent = videojs.Player
|
||||
dialog: any
|
||||
dialogEl: any
|
||||
menu: any
|
||||
panel: any
|
||||
panelChild: any
|
||||
dialog: SettingsDialog
|
||||
dialogEl: HTMLElement
|
||||
menu: videojs.Menu
|
||||
panel: SettingsPanel
|
||||
panelChild: SettingsPanelChild
|
||||
|
||||
addSettingsItemHandler: Function
|
||||
disposeSettingsItemHandler: Function
|
||||
playerClickHandler: Function
|
||||
userInactiveHandler: Function
|
||||
addSettingsItemHandler: typeof SettingsButton.prototype.onAddSettingsItem
|
||||
disposeSettingsItemHandler: typeof SettingsButton.prototype.onDisposeSettingsItem
|
||||
playerClickHandler: typeof SettingsButton.prototype.onPlayerClick
|
||||
userInactiveHandler: typeof SettingsButton.prototype.onUserInactive
|
||||
|
||||
constructor (player: videojs.Player, options: any) {
|
||||
private settingsButtonOptions: SettingsButtonOptions
|
||||
|
||||
constructor (player: VideoJsPlayer, options?: SettingsButtonOptions) {
|
||||
super(player, options)
|
||||
|
||||
this.playerComponent = player
|
||||
this.dialog = this.playerComponent.addChild('settingsDialog')
|
||||
this.dialogEl = this.dialog.el_
|
||||
this.settingsButtonOptions = options
|
||||
|
||||
this.controlText('Settings')
|
||||
|
||||
this.dialog = this.player().addChild('settingsDialog')
|
||||
this.dialogEl = this.dialog.el() as HTMLElement
|
||||
this.menu = null
|
||||
this.panel = this.dialog.addChild('settingsPanel')
|
||||
this.panelChild = this.panel.addChild('settingsPanelChild')
|
||||
|
||||
this.addClass('vjs-settings')
|
||||
this.el_.setAttribute('aria-label', 'Settings Button')
|
||||
this.el().setAttribute('aria-label', 'Settings Button')
|
||||
|
||||
// Event handlers
|
||||
this.addSettingsItemHandler = this.onAddSettingsItem.bind(this)
|
||||
|
@ -84,7 +93,7 @@ class SettingsButton extends Button {
|
|||
|
||||
this.hideDialog()
|
||||
|
||||
if (this.options_.entries.length === 0) {
|
||||
if (this.settingsButtonOptions.entries.length === 0) {
|
||||
this.addClass('vjs-hidden')
|
||||
}
|
||||
}
|
||||
|
@ -103,10 +112,10 @@ class SettingsButton extends Button {
|
|||
}
|
||||
|
||||
bindEvents () {
|
||||
this.playerComponent.on('click', this.playerClickHandler)
|
||||
this.playerComponent.on('addsettingsitem', this.addSettingsItemHandler)
|
||||
this.playerComponent.on('disposesettingsitem', this.disposeSettingsItemHandler)
|
||||
this.playerComponent.on('userinactive', this.userInactiveHandler)
|
||||
this.player().on('click', this.playerClickHandler)
|
||||
this.player().on('addsettingsitem', this.addSettingsItemHandler)
|
||||
this.player().on('disposesettingsitem', this.disposeSettingsItemHandler)
|
||||
this.player().on('userinactive', this.userInactiveHandler)
|
||||
}
|
||||
|
||||
buildCSSClass () {
|
||||
|
@ -122,9 +131,9 @@ class SettingsButton extends Button {
|
|||
}
|
||||
|
||||
showDialog () {
|
||||
this.player_.peertube().onMenuOpen()
|
||||
this.player().peertube().onMenuOpen();
|
||||
|
||||
this.menu.el_.style.opacity = '1'
|
||||
(this.menu.el() as HTMLElement).style.opacity = '1'
|
||||
this.dialog.show()
|
||||
|
||||
this.setDialogSize(this.getComponentSize(this.menu))
|
||||
|
@ -134,23 +143,24 @@ class SettingsButton extends Button {
|
|||
this.player_.peertube().onMenuClosed()
|
||||
|
||||
this.dialog.hide()
|
||||
this.setDialogSize(this.getComponentSize(this.menu))
|
||||
this.menu.el_.style.opacity = '1'
|
||||
this.setDialogSize(this.getComponentSize(this.menu));
|
||||
(this.menu.el() as HTMLElement).style.opacity = '1'
|
||||
this.resetChildren()
|
||||
}
|
||||
|
||||
getComponentSize (element: any) {
|
||||
getComponentSize (element: videojs.Component | HTMLElement) {
|
||||
let width: number = null
|
||||
let height: number = null
|
||||
|
||||
// Could be component or just DOM element
|
||||
if (element instanceof Component) {
|
||||
width = element.el_.offsetWidth
|
||||
height = element.el_.offsetHeight
|
||||
const el = element.el() as HTMLElement
|
||||
|
||||
// keep width/height as properties for direct use
|
||||
element.width = width
|
||||
element.height = height
|
||||
width = el.offsetWidth
|
||||
height = el.offsetHeight;
|
||||
|
||||
(element as any).width = width;
|
||||
(element as any).height = height
|
||||
} else {
|
||||
width = element.offsetWidth
|
||||
height = element.offsetHeight
|
||||
|
@ -164,15 +174,17 @@ class SettingsButton extends Button {
|
|||
return
|
||||
}
|
||||
|
||||
const offset = this.options_.setup.maxHeightOffset
|
||||
const maxHeight = this.playerComponent.el_.offsetHeight - offset
|
||||
const offset = this.settingsButtonOptions.setup.maxHeightOffset
|
||||
const maxHeight = (this.player().el() as HTMLElement).offsetHeight - offset // FIXME: typings
|
||||
|
||||
const panelEl = this.panel.el() as HTMLElement
|
||||
|
||||
if (height > maxHeight) {
|
||||
height = maxHeight
|
||||
width += 17
|
||||
this.panel.el_.style.maxHeight = `${height}px`
|
||||
} else if (this.panel.el_.style.maxHeight !== '') {
|
||||
this.panel.el_.style.maxHeight = ''
|
||||
panelEl.style.maxHeight = `${height}px`
|
||||
} else if (panelEl.style.maxHeight !== '') {
|
||||
panelEl.style.maxHeight = ''
|
||||
}
|
||||
|
||||
this.dialogEl.style.width = `${width}px`
|
||||
|
@ -182,7 +194,7 @@ class SettingsButton extends Button {
|
|||
buildMenu () {
|
||||
this.menu = new Menu(this.player())
|
||||
this.menu.addClass('vjs-main-menu')
|
||||
const entries = this.options_.entries
|
||||
const entries = this.settingsButtonOptions.entries
|
||||
|
||||
if (entries.length === 0) {
|
||||
this.addClass('vjs-hidden')
|
||||
|
@ -191,7 +203,7 @@ class SettingsButton extends Button {
|
|||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
this.addMenuItem(entry, this.options_)
|
||||
this.addMenuItem(entry, this.settingsButtonOptions)
|
||||
}
|
||||
|
||||
this.panelChild.addChild(this.menu)
|
||||
|
@ -199,15 +211,17 @@ class SettingsButton extends Button {
|
|||
|
||||
addMenuItem (entry: any, options: any) {
|
||||
const openSubMenu = function (this: any) {
|
||||
if (videojsUntyped.dom.hasClass(this.el_, 'open')) {
|
||||
videojsUntyped.dom.removeClass(this.el_, 'open')
|
||||
if (videojs.dom.hasClass(this.el_, 'open')) {
|
||||
videojs.dom.removeClass(this.el_, 'open')
|
||||
} else {
|
||||
videojsUntyped.dom.addClass(this.el_, 'open')
|
||||
videojs.dom.addClass(this.el_, 'open')
|
||||
}
|
||||
}
|
||||
|
||||
options.name = toTitleCase(entry)
|
||||
const settingsMenuItem = new SettingsMenuItem(this.player(), options, entry, this as any)
|
||||
|
||||
const newOptions = Object.assign({}, options, { entry, menuButton: this })
|
||||
const settingsMenuItem = new SettingsMenuItem(this.player(), newOptions)
|
||||
|
||||
this.menu.addChild(settingsMenuItem)
|
||||
|
||||
|
@ -221,7 +235,7 @@ class SettingsButton extends Button {
|
|||
|
||||
resetChildren () {
|
||||
for (const menuChild of this.menu.children()) {
|
||||
menuChild.reset()
|
||||
(menuChild as SettingsMenuItem).reset()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,75 +244,12 @@ class SettingsButton extends Button {
|
|||
*/
|
||||
hideChildren () {
|
||||
for (const menuChild of this.menu.children()) {
|
||||
menuChild.hideSubMenu()
|
||||
(menuChild as SettingsMenuItem).hideSubMenu()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SettingsPanel extends Component {
|
||||
constructor (player: videojs.Player, options: any) {
|
||||
super(player, options)
|
||||
}
|
||||
|
||||
createEl () {
|
||||
return super.createEl('div', {
|
||||
className: 'vjs-settings-panel',
|
||||
innerHTML: '',
|
||||
tabIndex: -1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsPanelChild extends Component {
|
||||
constructor (player: videojs.Player, options: any) {
|
||||
super(player, options)
|
||||
}
|
||||
|
||||
createEl () {
|
||||
return super.createEl('div', {
|
||||
className: 'vjs-settings-panel-child',
|
||||
innerHTML: '',
|
||||
tabIndex: -1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsDialog extends Component {
|
||||
constructor (player: videojs.Player, options: any) {
|
||||
super(player, options)
|
||||
this.hide()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the component's DOM element
|
||||
*
|
||||
* @return {Element}
|
||||
* @method createEl
|
||||
*/
|
||||
createEl () {
|
||||
const uniqueId = this.id_
|
||||
const dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId
|
||||
const dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId
|
||||
|
||||
return super.createEl('div', {
|
||||
className: 'vjs-settings-dialog vjs-modal-overlay',
|
||||
innerHTML: '',
|
||||
tabIndex: -1
|
||||
}, {
|
||||
'role': 'dialog',
|
||||
'aria-labelledby': dialogLabelId,
|
||||
'aria-describedby': dialogDescriptionId
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SettingsButton.prototype.controlText_ = 'Settings'
|
||||
|
||||
Component.registerComponent('SettingsButton', SettingsButton)
|
||||
Component.registerComponent('SettingsDialog', SettingsDialog)
|
||||
Component.registerComponent('SettingsPanel', SettingsPanel)
|
||||
Component.registerComponent('SettingsPanelChild', SettingsPanelChild)
|
||||
|
||||
export { SettingsButton, SettingsDialog, SettingsPanel, SettingsPanelChild }
|
||||
export { SettingsButton }
|
||||
|
|
|
@ -1,57 +1,63 @@
|
|||
// Author: Yanko Shterev
|
||||
// Thanks https://github.com/yshterev/videojs-settings-menu
|
||||
|
||||
// FIXME: something weird with our path definition in tsconfig and typings
|
||||
// @ts-ignore
|
||||
import * as videojs from 'video.js'
|
||||
|
||||
// Thanks to Yanko Shterev: https://github.com/yshterev/videojs-settings-menu
|
||||
import { toTitleCase } from '../utils'
|
||||
import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
|
||||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
import { SettingsButton } from './settings-menu-button'
|
||||
import { SettingsDialog } from './settings-dialog'
|
||||
import { SettingsPanel } from './settings-panel'
|
||||
import { SettingsPanelChild } from './settings-panel-child'
|
||||
|
||||
const MenuItem: VideoJSComponentInterface = videojsUntyped.getComponent('MenuItem')
|
||||
const component: VideoJSComponentInterface = videojsUntyped.getComponent('Component')
|
||||
const MenuItem = videojs.getComponent('MenuItem')
|
||||
const component = videojs.getComponent('Component')
|
||||
|
||||
export interface SettingsMenuItemOptions extends videojs.MenuItemOptions {
|
||||
entry: string
|
||||
menuButton: SettingsButton
|
||||
}
|
||||
|
||||
class SettingsMenuItem extends MenuItem {
|
||||
settingsButton: any
|
||||
dialog: any
|
||||
mainMenu: any
|
||||
panel: any
|
||||
panelChild: any
|
||||
panelChildEl: any
|
||||
size: any
|
||||
settingsButton: SettingsButton
|
||||
dialog: SettingsDialog
|
||||
mainMenu: videojs.Menu
|
||||
panel: SettingsPanel
|
||||
panelChild: SettingsPanelChild
|
||||
panelChildEl: HTMLElement
|
||||
size: number[]
|
||||
menuToLoad: string
|
||||
subMenu: any
|
||||
subMenu: SettingsButton
|
||||
|
||||
submenuClickHandler: Function
|
||||
transitionEndHandler: Function
|
||||
submenuClickHandler: typeof SettingsMenuItem.prototype.onSubmenuClick
|
||||
transitionEndHandler: typeof SettingsMenuItem.prototype.onTransitionEnd
|
||||
|
||||
settingsSubMenuTitleEl_: any
|
||||
settingsSubMenuValueEl_: any
|
||||
settingsSubMenuEl_: any
|
||||
settingsSubMenuTitleEl_: HTMLElement
|
||||
settingsSubMenuValueEl_: HTMLElement
|
||||
settingsSubMenuEl_: HTMLElement
|
||||
|
||||
constructor (player: videojs.Player, options: any, entry: string, menuButton: VideoJSComponentInterface) {
|
||||
constructor (player: VideoJsPlayer, options?: SettingsMenuItemOptions) {
|
||||
super(player, options)
|
||||
|
||||
this.settingsButton = menuButton
|
||||
this.settingsButton = options.menuButton
|
||||
this.dialog = this.settingsButton.dialog
|
||||
this.mainMenu = this.settingsButton.menu
|
||||
this.panel = this.dialog.getChild('settingsPanel')
|
||||
this.panelChild = this.panel.getChild('settingsPanelChild')
|
||||
this.panelChildEl = this.panelChild.el_
|
||||
this.panelChildEl = this.panelChild.el() as HTMLElement
|
||||
|
||||
this.size = null
|
||||
|
||||
// keep state of what menu type is loading next
|
||||
this.menuToLoad = 'mainmenu'
|
||||
|
||||
const subMenuName = toTitleCase(entry)
|
||||
const SubMenuComponent = videojsUntyped.getComponent(subMenuName)
|
||||
const subMenuName = toTitleCase(options.entry)
|
||||
const SubMenuComponent = videojs.getComponent(subMenuName)
|
||||
|
||||
if (!SubMenuComponent) {
|
||||
throw new Error(`Component ${subMenuName} does not exist`)
|
||||
}
|
||||
this.subMenu = new SubMenuComponent(this.player(), options, menuButton, this)
|
||||
const subMenuClass = this.subMenu.buildCSSClass().split(' ')[0]
|
||||
|
||||
const newOptions = Object.assign({}, options, { entry: options.menuButton, menuButton: this })
|
||||
|
||||
this.subMenu = new SubMenuComponent(this.player(), newOptions) as any // FIXME: typings
|
||||
const subMenuClass = this.subMenu.buildCSSClass().split(' ')[ 0 ]
|
||||
this.settingsSubMenuEl_.className += ' ' + subMenuClass
|
||||
|
||||
this.eventHandlers()
|
||||
|
@ -72,7 +78,7 @@ class SettingsMenuItem extends MenuItem {
|
|||
player.on('captionsChanged', () => {
|
||||
setTimeout(() => {
|
||||
this.settingsSubMenuEl_.innerHTML = ''
|
||||
this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_)
|
||||
this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el())
|
||||
this.update()
|
||||
this.bindClickEvents()
|
||||
}, 0)
|
||||
|
@ -119,27 +125,27 @@ class SettingsMenuItem extends MenuItem {
|
|||
* @method createEl
|
||||
*/
|
||||
createEl () {
|
||||
const el = videojsUntyped.dom.createEl('li', {
|
||||
const el = videojs.dom.createEl('li', {
|
||||
className: 'vjs-menu-item'
|
||||
})
|
||||
|
||||
this.settingsSubMenuTitleEl_ = videojsUntyped.dom.createEl('div', {
|
||||
this.settingsSubMenuTitleEl_ = videojs.dom.createEl('div', {
|
||||
className: 'vjs-settings-sub-menu-title'
|
||||
})
|
||||
}) as HTMLElement
|
||||
|
||||
el.appendChild(this.settingsSubMenuTitleEl_)
|
||||
|
||||
this.settingsSubMenuValueEl_ = videojsUntyped.dom.createEl('div', {
|
||||
this.settingsSubMenuValueEl_ = videojs.dom.createEl('div', {
|
||||
className: 'vjs-settings-sub-menu-value'
|
||||
})
|
||||
}) as HTMLElement
|
||||
|
||||
el.appendChild(this.settingsSubMenuValueEl_)
|
||||
|
||||
this.settingsSubMenuEl_ = videojsUntyped.dom.createEl('div', {
|
||||
this.settingsSubMenuEl_ = videojs.dom.createEl('div', {
|
||||
className: 'vjs-settings-sub-menu'
|
||||
})
|
||||
}) as HTMLElement
|
||||
|
||||
return el
|
||||
return el as HTMLLIElement
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -147,17 +153,17 @@ class SettingsMenuItem extends MenuItem {
|
|||
*
|
||||
* @method handleClick
|
||||
*/
|
||||
handleClick () {
|
||||
handleClick (event: videojs.EventTarget.Event) {
|
||||
this.menuToLoad = 'submenu'
|
||||
// Remove open class to ensure only the open submenu gets this class
|
||||
videojsUntyped.dom.removeClass(this.el_, 'open')
|
||||
videojs.dom.removeClass(this.el(), 'open')
|
||||
|
||||
super.handleClick()
|
||||
super.handleClick(event);
|
||||
|
||||
this.mainMenu.el_.style.opacity = '0'
|
||||
(this.mainMenu.el() as HTMLElement).style.opacity = '0'
|
||||
// Whether to add or remove vjs-hidden class on the settingsSubMenuEl element
|
||||
if (videojsUntyped.dom.hasClass(this.settingsSubMenuEl_, 'vjs-hidden')) {
|
||||
videojsUntyped.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||
if (videojs.dom.hasClass(this.settingsSubMenuEl_, 'vjs-hidden')) {
|
||||
videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||
|
||||
// animation not played without timeout
|
||||
setTimeout(() => {
|
||||
|
@ -167,7 +173,7 @@ class SettingsMenuItem extends MenuItem {
|
|||
|
||||
this.settingsButton.setDialogSize(this.size)
|
||||
} else {
|
||||
videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||
videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,9 +184,9 @@ class SettingsMenuItem extends MenuItem {
|
|||
*/
|
||||
createBackButton () {
|
||||
const button = this.subMenu.menu.addChild('MenuItem', {}, 0)
|
||||
button.name_ = 'BackButton'
|
||||
button.addClass('vjs-back-button')
|
||||
button.el_.innerHTML = this.player_.localize(this.subMenu.controlText_)
|
||||
|
||||
button.addClass('vjs-back-button');
|
||||
(button.el() as HTMLElement).innerHTML = this.player().localize(this.subMenu.controlText())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -189,17 +195,17 @@ class SettingsMenuItem extends MenuItem {
|
|||
* @method PrefixedEvent
|
||||
*/
|
||||
PrefixedEvent (element: any, type: any, callback: any, action = 'addEvent') {
|
||||
const prefix = ['webkit', 'moz', 'MS', 'o', '']
|
||||
const prefix = [ 'webkit', 'moz', 'MS', 'o', '' ]
|
||||
|
||||
for (let p = 0; p < prefix.length; p++) {
|
||||
if (!prefix[p]) {
|
||||
if (!prefix[ p ]) {
|
||||
type = type.toLowerCase()
|
||||
}
|
||||
|
||||
if (action === 'addEvent') {
|
||||
element.addEventListener(prefix[p] + type, callback, false)
|
||||
element.addEventListener(prefix[ p ] + type, callback, false)
|
||||
} else if (action === 'removeEvent') {
|
||||
element.removeEventListener(prefix[p] + type, callback, false)
|
||||
element.removeEventListener(prefix[ p ] + type, callback, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -211,7 +217,7 @@ class SettingsMenuItem extends MenuItem {
|
|||
|
||||
if (this.menuToLoad === 'mainmenu') {
|
||||
// hide submenu
|
||||
videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||
videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||
|
||||
// reset opacity to 0
|
||||
this.settingsSubMenuEl_.style.opacity = '0'
|
||||
|
@ -219,25 +225,27 @@ class SettingsMenuItem extends MenuItem {
|
|||
}
|
||||
|
||||
reset () {
|
||||
videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||
videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||
this.settingsSubMenuEl_.style.opacity = '0'
|
||||
this.setMargin()
|
||||
}
|
||||
|
||||
loadMainMenu () {
|
||||
const mainMenuEl = this.mainMenu.el() as HTMLElement
|
||||
this.menuToLoad = 'mainmenu'
|
||||
this.mainMenu.show()
|
||||
this.mainMenu.el_.style.opacity = '0'
|
||||
mainMenuEl.style.opacity = '0'
|
||||
|
||||
// back button will always take you to main menu, so set dialog sizes
|
||||
this.settingsButton.setDialogSize([this.mainMenu.width, this.mainMenu.height])
|
||||
const mainMenuAny = this.mainMenu as any
|
||||
this.settingsButton.setDialogSize([ mainMenuAny.width, mainMenuAny.height ])
|
||||
|
||||
// animation not triggered without timeout (some async stuff ?!?)
|
||||
setTimeout(() => {
|
||||
// animate margin and opacity before hiding the submenu
|
||||
// this triggers CSS Transition event
|
||||
this.setMargin()
|
||||
this.mainMenu.el_.style.opacity = '1'
|
||||
mainMenuEl.style.opacity = '1'
|
||||
}, 0)
|
||||
}
|
||||
|
||||
|
@ -251,8 +259,8 @@ class SettingsMenuItem extends MenuItem {
|
|||
this.update()
|
||||
})
|
||||
|
||||
this.settingsSubMenuTitleEl_.innerHTML = this.player_.localize(this.subMenu.controlText_)
|
||||
this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_)
|
||||
this.settingsSubMenuTitleEl_.innerHTML = this.player().localize(this.subMenu.controlText())
|
||||
this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el())
|
||||
this.panelChildEl.appendChild(this.settingsSubMenuEl_)
|
||||
this.update()
|
||||
|
||||
|
@ -283,7 +291,8 @@ class SettingsMenuItem extends MenuItem {
|
|||
// or sets options_['selected'] on the selected playback rate.
|
||||
// Thus we get the submenu value based on the labelEl of playbackRateMenuButton
|
||||
if (subMenu === 'PlaybackRateMenuButton') {
|
||||
setTimeout(() => this.settingsSubMenuValueEl_.innerHTML = this.subMenu.labelEl_.innerHTML, 250)
|
||||
const html = (this.subMenu as any).labelEl_.innerHTML
|
||||
setTimeout(() => this.settingsSubMenuValueEl_.innerHTML = html, 250)
|
||||
} else {
|
||||
// Loop trough the submenu items to find the selected child
|
||||
for (const subMenuItem of this.subMenu.menu.children_) {
|
||||
|
@ -292,13 +301,15 @@ class SettingsMenuItem extends MenuItem {
|
|||
}
|
||||
|
||||
if (subMenuItem.hasClass('vjs-selected')) {
|
||||
const subMenuItemUntyped = subMenuItem as any
|
||||
|
||||
// Prefer to use the function
|
||||
if (typeof subMenuItem.getLabel === 'function') {
|
||||
this.settingsSubMenuValueEl_.innerHTML = subMenuItem.getLabel()
|
||||
if (typeof subMenuItemUntyped.getLabel === 'function') {
|
||||
this.settingsSubMenuValueEl_.innerHTML = subMenuItemUntyped.getLabel()
|
||||
break
|
||||
}
|
||||
|
||||
this.settingsSubMenuValueEl_.innerHTML = subMenuItem.options_.label
|
||||
this.settingsSubMenuValueEl_.innerHTML = subMenuItemUntyped.options_.label
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -313,7 +324,7 @@ class SettingsMenuItem extends MenuItem {
|
|||
if (!(item instanceof component)) {
|
||||
continue
|
||||
}
|
||||
item.on(['tap', 'click'], this.submenuClickHandler)
|
||||
item.on([ 'tap', 'click' ], this.submenuClickHandler)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -321,11 +332,11 @@ class SettingsMenuItem extends MenuItem {
|
|||
// if number of submenu items change dynamically more logic will be needed
|
||||
setSize () {
|
||||
this.dialog.removeClass('vjs-hidden')
|
||||
videojsUntyped.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||
videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||
this.size = this.settingsButton.getComponentSize(this.settingsSubMenuEl_)
|
||||
this.setMargin()
|
||||
this.dialog.addClass('vjs-hidden')
|
||||
videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||
videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||
}
|
||||
|
||||
setMargin () {
|
||||
|
@ -341,19 +352,19 @@ class SettingsMenuItem extends MenuItem {
|
|||
*/
|
||||
hideSubMenu () {
|
||||
// after removing settings item this.el_ === null
|
||||
if (!this.el_) {
|
||||
if (!this.el()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (videojsUntyped.dom.hasClass(this.el_, 'open')) {
|
||||
videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||
videojsUntyped.dom.removeClass(this.el_, 'open')
|
||||
if (videojs.dom.hasClass(this.el(), 'open')) {
|
||||
videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
|
||||
videojs.dom.removeClass(this.el(), 'open')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SettingsMenuItem.prototype.contentElType = 'button'
|
||||
videojsUntyped.registerComponent('SettingsMenuItem', SettingsMenuItem)
|
||||
(SettingsMenuItem as any).prototype.contentElType = 'button'
|
||||
videojs.registerComponent('SettingsMenuItem', SettingsMenuItem)
|
||||
|
||||
export { SettingsMenuItem }
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
|
||||
const Component = videojs.getComponent('Component')
|
||||
|
||||
class SettingsPanelChild extends Component {
|
||||
|
||||
constructor (player: VideoJsPlayer, options?: videojs.ComponentOptions) {
|
||||
super(player, options)
|
||||
}
|
||||
|
||||
createEl () {
|
||||
return super.createEl('div', {
|
||||
className: 'vjs-settings-panel-child',
|
||||
innerHTML: '',
|
||||
tabIndex: -1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Component.registerComponent('SettingsPanelChild', SettingsPanelChild)
|
||||
|
||||
export { SettingsPanelChild }
|
|
@ -0,0 +1,22 @@
|
|||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
|
||||
const Component = videojs.getComponent('Component')
|
||||
|
||||
class SettingsPanel extends Component {
|
||||
|
||||
constructor (player: VideoJsPlayer, options?: videojs.ComponentOptions) {
|
||||
super(player, options)
|
||||
}
|
||||
|
||||
createEl () {
|
||||
return super.createEl('div', {
|
||||
className: 'vjs-settings-panel',
|
||||
innerHTML: '',
|
||||
tabIndex: -1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Component.registerComponent('SettingsPanel', SettingsPanel)
|
||||
|
||||
export { SettingsPanel }
|
|
@ -1,26 +1,24 @@
|
|||
// FIXME: something weird with our path definition in tsconfig and typings
|
||||
// @ts-ignore
|
||||
import * as videojs from 'video.js'
|
||||
|
||||
import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
|
||||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
import { saveTheaterInStore, getStoredTheater } from '../peertube-player-local-storage'
|
||||
|
||||
const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button')
|
||||
const Button = videojs.getComponent('Button')
|
||||
class TheaterButton extends Button {
|
||||
|
||||
private static readonly THEATER_MODE_CLASS = 'vjs-theater-enabled'
|
||||
|
||||
constructor (player: videojs.Player, options: any) {
|
||||
constructor (player: VideoJsPlayer, options: videojs.ComponentOptions) {
|
||||
super(player, options)
|
||||
|
||||
const enabled = getStoredTheater()
|
||||
if (enabled === true) {
|
||||
this.player_.addClass(TheaterButton.THEATER_MODE_CLASS)
|
||||
this.player().addClass(TheaterButton.THEATER_MODE_CLASS)
|
||||
|
||||
this.handleTheaterChange()
|
||||
}
|
||||
|
||||
this.player_.theaterEnabled = enabled
|
||||
this.controlText('Theater mode')
|
||||
|
||||
this.player().theaterEnabled = enabled
|
||||
}
|
||||
|
||||
buildCSSClass () {
|
||||
|
@ -52,6 +50,4 @@ class TheaterButton extends Button {
|
|||
}
|
||||
}
|
||||
|
||||
TheaterButton.prototype.controlText_ = 'Theater mode'
|
||||
|
||||
TheaterButton.registerComponent('TheaterButton', TheaterButton)
|
||||
videojs.registerComponent('TheaterButton', TheaterButton)
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
// FIXME: something weird with our path definition in tsconfig and typings
|
||||
// @ts-ignore
|
||||
import * as videojs from 'video.js'
|
||||
import videojs, { VideoJsPlayer } from 'video.js'
|
||||
|
||||
import * as WebTorrent from 'webtorrent'
|
||||
import { renderVideo } from './video-renderer'
|
||||
import { LoadedQualityData, PlayerNetworkInfo, VideoJSComponentInterface, WebtorrentPluginOptions } from '../peertube-videojs-typings'
|
||||
import { LoadedQualityData, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings'
|
||||
import { getRtcConfig, timeToInt, videoFileMaxByResolution, videoFileMinByResolution } from '../utils'
|
||||
import { PeertubeChunkStore } from './peertube-chunk-store'
|
||||
import {
|
||||
getAverageBandwidthInStore,
|
||||
getStoredMute,
|
||||
getStoredVolume,
|
||||
getStoredP2PEnabled,
|
||||
getStoredVolume,
|
||||
saveAverageBandwidth
|
||||
} from '../peertube-player-local-storage'
|
||||
import { VideoFile } from '@shared/models'
|
||||
|
@ -24,13 +22,14 @@ type PlayOptions = {
|
|||
delay?: number
|
||||
}
|
||||
|
||||
const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin')
|
||||
const Plugin = videojs.getPlugin('plugin')
|
||||
|
||||
class WebTorrentPlugin extends Plugin {
|
||||
private readonly playerElement: HTMLVideoElement
|
||||
|
||||
private readonly autoplay: boolean = false
|
||||
private readonly startTime: number = 0
|
||||
private readonly savePlayerSrcFunction: Function
|
||||
private readonly savePlayerSrcFunction: VideoJsPlayer['src']
|
||||
private readonly videoFiles: VideoFile[]
|
||||
private readonly videoDuration: number
|
||||
private readonly CONSTANTS = {
|
||||
|
@ -49,7 +48,6 @@ class WebTorrentPlugin extends Plugin {
|
|||
dht: false
|
||||
})
|
||||
|
||||
private player: any
|
||||
private currentVideoFile: VideoFile
|
||||
private torrent: WebTorrent.Torrent
|
||||
|
||||
|
@ -70,8 +68,8 @@ class WebTorrentPlugin extends Plugin {
|
|||
|
||||
private downloadSpeeds: number[] = []
|
||||
|
||||
constructor (player: videojs.Player, options: WebtorrentPluginOptions) {
|
||||
super(player, options)
|
||||
constructor (player: VideoJsPlayer, options?: WebtorrentPluginOptions) {
|
||||
super(player)
|
||||
|
||||
this.startTime = timeToInt(options.startTime)
|
||||
|
||||
|
@ -147,12 +145,12 @@ class WebTorrentPlugin extends Plugin {
|
|||
}
|
||||
|
||||
// Do not display error to user because we will have multiple fallback
|
||||
this.disableErrorDisplay()
|
||||
this.disableErrorDisplay();
|
||||
|
||||
// Hack to "simulate" src link in video.js >= 6
|
||||
// Without this, we can't play the video after pausing it
|
||||
// https://github.com/videojs/video.js/blob/master/src/js/player.js#L1633
|
||||
this.player.src = () => true
|
||||
(this.player as any).src = () => true
|
||||
const oldPlaybackRate = this.player.playbackRate()
|
||||
|
||||
const previousVideoFile = this.currentVideoFile
|
||||
|
@ -333,7 +331,7 @@ class WebTorrentPlugin extends Plugin {
|
|||
|
||||
const playPromise = this.player.play()
|
||||
if (playPromise !== undefined) {
|
||||
return playPromise.then(done)
|
||||
return playPromise.then(() => done())
|
||||
.catch((err: Error) => {
|
||||
if (err.message.indexOf('The play() request was interrupted by a call to pause()') !== -1) {
|
||||
return
|
||||
|
@ -426,8 +424,8 @@ class WebTorrentPlugin extends Plugin {
|
|||
}
|
||||
|
||||
// Proxy first play
|
||||
const oldPlay = this.player.play.bind(this.player)
|
||||
this.player.play = () => {
|
||||
const oldPlay = this.player.play.bind(this.player);
|
||||
(this.player as any).play = () => {
|
||||
this.player.addClass('vjs-has-big-play-button-clicked')
|
||||
this.player.play = oldPlay
|
||||
|
||||
|
@ -619,7 +617,7 @@ class WebTorrentPlugin extends Plugin {
|
|||
video: qualityLevelsPayload
|
||||
}
|
||||
}
|
||||
this.player.tech_.trigger('loadedqualitydata', payload)
|
||||
this.player.tech(true).trigger('loadedqualitydata', payload)
|
||||
}
|
||||
|
||||
private buildQualityLabel (file: VideoFile) {
|
||||
|
@ -651,7 +649,7 @@ class WebTorrentPlugin extends Plugin {
|
|||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < qualityLevels; i++) {
|
||||
for (let i = 0; i < qualityLevels.length; i++) {
|
||||
const q = this.player.qualityLevels[i]
|
||||
if (q.height === resolutionId) qualityLevels.selectedIndex = i
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
"paths": {
|
||||
"@app/*": [ "src/app/*" ],
|
||||
"@shared/*": [ "../shared/*" ],
|
||||
"video.js": [ "node_modules/video.js/dist/alt/video.core.js" ],
|
||||
"video.js": [ "node_modules/video.js/dist/alt/video.core.novtt" ],
|
||||
"fs": [ "src/shims/noop" ],
|
||||
"http": [ "src/shims/http" ],
|
||||
"https": [ "src/shims/https" ],
|
||||
|
|
|
@ -27,7 +27,7 @@ module.exports = function () {
|
|||
modules: [ helpers.root('src'), helpers.root('node_modules') ],
|
||||
|
||||
alias: {
|
||||
'video.js$': path.resolve('node_modules/video.js/dist/alt/video.core.js')
|
||||
'video.js$': path.resolve('node_modules/video.js/dist/alt/video.core.novtt.js')
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1157,10 +1157,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
|
||||
integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==
|
||||
|
||||
"@types/video.js@^7.2.5":
|
||||
version "7.2.15"
|
||||
resolved "https://registry.yarnpkg.com/@types/video.js/-/video.js-7.2.15.tgz#03d950f01c985a5082ead4d1b73064455a1c8c6f"
|
||||
integrity sha512-NsojVfvTwdVqDe0+vJaoHOO2iuLm0sp/u8jEsZeLGsM3gNfg5WIOFd6NC0cQR9JHUuDPPSPF70jxdklGWm5jhQ==
|
||||
"@types/video.js@^7.3.3":
|
||||
version "7.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/video.js/-/video.js-7.3.3.tgz#b6870d954473dfd694e10b55a90c0f3be8522da3"
|
||||
integrity sha512-yAb46+4A0dKFxOQRVLoLyfC/S/BmHLE10MxPXt/t88+7R4GWLHosHelVtYpKBRykjptdkqfQXNRXoQzDeKm6MA==
|
||||
|
||||
"@types/webpack-sources@^0.1.5":
|
||||
version "0.1.5"
|
||||
|
@ -10842,6 +10842,11 @@ void-elements@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
|
||||
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
|
||||
|
||||
vtt.js@^0.13.0:
|
||||
version "0.13.0"
|
||||
resolved "https://registry.yarnpkg.com/vtt.js/-/vtt.js-0.13.0.tgz#955c667b34d5325b2012cb9e8ba9bad6e0b11ff8"
|
||||
integrity sha1-lVxmezTVMlsgEsuei6m61uCxH/g=
|
||||
|
||||
watchpack@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"
|
||||
|
|
Loading…
Reference in New Issue