From f5fcd9f72514d6c4044a9c904d0ce610033bcba5 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 28 Jan 2020 17:29:50 +0100 Subject: [PATCH] Correctly type videojs player --- client/package.json | 3 +- .../src/assets/player/bezels/bezels-plugin.ts | 86 +------- .../src/assets/player/bezels/pause-bezel.ts | 72 +++++++ .../p2p-media-loader-plugin.ts | 23 ++- .../assets/player/peertube-player-manager.ts | 64 +++--- client/src/assets/player/peertube-plugin.ts | 22 +- .../assets/player/peertube-videojs-typings.ts | 50 +++-- client/src/assets/player/upnext/end-card.ts | 155 ++++++++++++++ .../src/assets/player/upnext/upnext-plugin.ts | 155 +------------- .../videojs-components/next-video-button.ts | 29 +-- .../videojs-components/p2p-info-button.ts | 48 ++--- .../peertube-link-button.ts | 20 +- .../peertube-load-progress-bar.ts | 17 +- .../resolution-menu-button.ts | 32 ++- .../resolution-menu-item.ts | 36 ++-- .../videojs-components/settings-dialog.ts | 37 ++++ .../settings-menu-button.ts | 191 +++++++----------- .../videojs-components/settings-menu-item.ts | 159 ++++++++------- .../settings-panel-child.ts | 22 ++ .../videojs-components/settings-panel.ts | 22 ++ .../videojs-components/theater-button.ts | 20 +- .../player/webtorrent/webtorrent-plugin.ts | 32 ++- client/tsconfig.json | 2 +- client/webpack/webpack.video-embed.js | 2 +- client/yarn.lock | 13 +- 25 files changed, 695 insertions(+), 617 deletions(-) create mode 100644 client/src/assets/player/bezels/pause-bezel.ts create mode 100644 client/src/assets/player/upnext/end-card.ts create mode 100644 client/src/assets/player/videojs-components/settings-dialog.ts create mode 100644 client/src/assets/player/videojs-components/settings-panel-child.ts create mode 100644 client/src/assets/player/videojs-components/settings-panel.ts diff --git a/client/package.json b/client/package.json index 7205dbe8f..e9c74787c 100644 --- a/client/package.json +++ b/client/package.json @@ -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", diff --git a/client/src/assets/player/bezels/bezels-plugin.ts b/client/src/assets/player/bezels/bezels-plugin.ts index c2c251961..499177526 100644 --- a/client/src/assets/player/bezels/bezels-plugin.ts +++ b/client/src/assets/player/bezels/bezels-plugin.ts @@ -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 ` -
-
-
- - - - -
-
-
- ` -} - -function getPlayBezel () { - return ` -
-
-
- - - - -
-
-
- ` -} - -// @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() - }) - } +const Plugin = videojs.getPlugin('plugin') - 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 } diff --git a/client/src/assets/player/bezels/pause-bezel.ts b/client/src/assets/player/bezels/pause-bezel.ts new file mode 100644 index 000000000..98eb12099 --- /dev/null +++ b/client/src/assets/player/bezels/pause-bezel.ts @@ -0,0 +1,72 @@ +import videojs, { VideoJsPlayer } from 'video.js' + +function getPauseBezel () { + return ` +
+
+
+ + + + +
+
+
+ ` +} + +function getPlayBezel () { + return ` +
+
+
+ + + + +
+
+
+ ` +} + +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) diff --git a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts index c3f863f72..512054ae6 100644 --- a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts +++ b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts @@ -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 diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts index d9e02cd7d..4e6387a53 100644 --- a/client/src/assets/player/peertube-player-manager.ts +++ b/client/src/assets/player/peertube-player-manager.ts @@ -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/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/p2p-info-button' -import './videojs-components/peertube-load-progress-bar' +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: { diff --git a/client/src/assets/player/peertube-plugin.ts b/client/src/assets/player/peertube-plugin.ts index 9824c43b5..19d104676 100644 --- a/client/src/assets/player/peertube-plugin.ts +++ b/client/src/assets/player/peertube-plugin.ts @@ -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()) ? diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts index aad4dbb4f..7b0ea2074 100644 --- a/client/src/assets/player/peertube-videojs-typings.ts +++ b/client/src/assets/player/peertube-videojs-typings.ts @@ -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,20 +6,44 @@ 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 - } -} -interface VideoJSComponentInterface { - _player: videojs.Player + contextmenuUI (options: any): any + + bezels (): void + + qualityLevels (): { height: number, id: number }[] & { + selectedIndex: number - new (player: videojs.Player, options?: any): any + addQualityLevel (representation: { + id: number + label: string + height: number, + _enabled: boolean + }): void + } - registerComponent (name: string, obj: any): any + textTracks (): TextTrackList & { + on: Function + tracks_: { kind: string, mode: string, language: string }[] + } + } } type VideoJSCaption = { @@ -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, diff --git a/client/src/assets/player/upnext/end-card.ts b/client/src/assets/player/upnext/end-card.ts new file mode 100644 index 000000000..d121a83a9 --- /dev/null +++ b/client/src/assets/player/upnext/end-card.ts @@ -0,0 +1,155 @@ +import videojs, { VideoJsPlayer } from 'video.js' + +function getMainTemplate (options: any) { + return ` +
+ ${options.headText} +
+
+
+ + + + +
+ + + + + ${options.suspendedText} + + ` +} + +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) diff --git a/client/src/assets/player/upnext/upnext-plugin.ts b/client/src/assets/player/upnext/upnext-plugin.ts index a3747b25f..6512fec2c 100644 --- a/client/src/assets/player/upnext/upnext-plugin.ts +++ b/client/src/assets/player/upnext/upnext-plugin.ts @@ -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 ` -
- ${options.headText} -
-
-
- - - - -
- - - - - ${options.suspendedText} - - ` -} - -// @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 - } +const Plugin = videojs.getPlugin('plugin') - 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 = {}) { 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') diff --git a/client/src/assets/player/videojs-components/next-video-button.ts b/client/src/assets/player/videojs-components/next-video-button.ts index bf5c1aba4..bdb245dcc 100644 --- a/client/src/assets/player/videojs-components/next-video-button.ts +++ b/client/src/assets/player/videojs-components/next-video-button.ts @@ -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) diff --git a/client/src/assets/player/videojs-components/p2p-info-button.ts b/client/src/assets/player/videojs-components/p2p-info-button.ts index 6424787b2..db6806fed 100644 --- a/client/src/assets/player/videojs-components/p2p-info-button.ts +++ b/client/src/assets/player/videojs-components/p2p-info-button.ts @@ -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) diff --git a/client/src/assets/player/videojs-components/peertube-link-button.ts b/client/src/assets/player/videojs-components/peertube-link-button.ts index 4d0ea37f5..0db9762a5 100644 --- a/client/src/assets/player/videojs-components/peertube-link-button.ts +++ b/client/src/assets/player/videojs-components/peertube-link-button.ts @@ -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) diff --git a/client/src/assets/player/videojs-components/peertube-load-progress-bar.ts b/client/src/assets/player/videojs-components/peertube-load-progress-bar.ts index b594fc1c5..8168e8f2d 100644 --- a/client/src/assets/player/videojs-components/peertube-load-progress-bar.ts +++ b/client/src/assets/player/videojs-components/peertube-load-progress-bar.ts @@ -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) + '%' } } diff --git a/client/src/assets/player/videojs-components/resolution-menu-button.ts b/client/src/assets/player/videojs-components/resolution-menu-button.ts index 86be03af7..af044a9e5 100644 --- a/client/src/assets/player/videojs-components/resolution-menu-button.ts +++ b/client/src/assets/player/videojs-components/resolution-menu-button.ts @@ -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) diff --git a/client/src/assets/player/videojs-components/resolution-menu-item.ts b/client/src/assets/player/videojs-components/resolution-menu-item.ts index 6c42fefd2..b039c4572 100644 --- a/client/src/assets/player/videojs-components/resolution-menu-item.ts +++ b/client/src/assets/player/videojs-components/resolution-menu-item.ts @@ -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 + ' ' + this.currentResolutionLabel + '' } return this.label } } -MenuItem.registerComponent('ResolutionMenuItem', ResolutionMenuItem) +videojs.registerComponent('ResolutionMenuItem', ResolutionMenuItem) export { ResolutionMenuItem } diff --git a/client/src/assets/player/videojs-components/settings-dialog.ts b/client/src/assets/player/videojs-components/settings-dialog.ts new file mode 100644 index 000000000..dd0b1e472 --- /dev/null +++ b/client/src/assets/player/videojs-components/settings-dialog.ts @@ -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 } diff --git a/client/src/assets/player/videojs-components/settings-menu-button.ts b/client/src/assets/player/videojs-components/settings-menu-button.ts index b700f4be6..eae628e7d 100644 --- a/client/src/assets/player/videojs-components/settings-menu-button.ts +++ b/client/src/assets/player/videojs-components/settings-menu-button.ts @@ -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' + +import { SettingsDialog } from './settings-dialog' +import { SettingsPanel } from './settings-panel' +import { SettingsPanelChild } from './settings-panel-child' -const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button') -const Menu: VideoJSComponentInterface = videojsUntyped.getComponent('Menu') -const Component: VideoJSComponentInterface = videojsUntyped.getComponent('Component') +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 - - addSettingsItemHandler: Function - disposeSettingsItemHandler: Function - playerClickHandler: Function - userInactiveHandler: Function - - constructor (player: videojs.Player, options: any) { + dialog: SettingsDialog + dialogEl: HTMLElement + menu: videojs.Menu + panel: SettingsPanel + panelChild: SettingsPanelChild + + addSettingsItemHandler: typeof SettingsButton.prototype.onAddSettingsItem + disposeSettingsItemHandler: typeof SettingsButton.prototype.onDisposeSettingsItem + playerClickHandler: typeof SettingsButton.prototype.onPlayerClick + userInactiveHandler: typeof SettingsButton.prototype.onUserInactive + + 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 + + width = el.offsetWidth + height = el.offsetHeight; - // keep width/height as properties for direct use - element.width = width - element.height = height + (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 } diff --git a/client/src/assets/player/videojs-components/settings-menu-item.ts b/client/src/assets/player/videojs-components/settings-menu-item.ts index 84d394c0e..f5671f49d 100644 --- a/client/src/assets/player/videojs-components/settings-menu-item.ts +++ b/client/src/assets/player/videojs-components/settings-menu-item.ts @@ -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' - -const MenuItem: VideoJSComponentInterface = videojsUntyped.getComponent('MenuItem') -const component: VideoJSComponentInterface = videojsUntyped.getComponent('Component') +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 = 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 } diff --git a/client/src/assets/player/videojs-components/settings-panel-child.ts b/client/src/assets/player/videojs-components/settings-panel-child.ts new file mode 100644 index 000000000..d12e8218a --- /dev/null +++ b/client/src/assets/player/videojs-components/settings-panel-child.ts @@ -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 } diff --git a/client/src/assets/player/videojs-components/settings-panel.ts b/client/src/assets/player/videojs-components/settings-panel.ts new file mode 100644 index 000000000..2090abf45 --- /dev/null +++ b/client/src/assets/player/videojs-components/settings-panel.ts @@ -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 } diff --git a/client/src/assets/player/videojs-components/theater-button.ts b/client/src/assets/player/videojs-components/theater-button.ts index bf383cf34..1c8c9f154 100644 --- a/client/src/assets/player/videojs-components/theater-button.ts +++ b/client/src/assets/player/videojs-components/theater-button.ts @@ -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) diff --git a/client/src/assets/player/webtorrent/webtorrent-plugin.ts b/client/src/assets/player/webtorrent/webtorrent-plugin.ts index 35cf85c99..3d335acbc 100644 --- a/client/src/assets/player/webtorrent/webtorrent-plugin.ts +++ b/client/src/assets/player/webtorrent/webtorrent-plugin.ts @@ -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 } diff --git a/client/tsconfig.json b/client/tsconfig.json index 8824c4f7c..c4f2d6a6a 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -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" ], diff --git a/client/webpack/webpack.video-embed.js b/client/webpack/webpack.video-embed.js index 909048cca..f6d532556 100644 --- a/client/webpack/webpack.video-embed.js +++ b/client/webpack/webpack.video-embed.js @@ -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') } }, diff --git a/client/yarn.lock b/client/yarn.lock index c900ae549..20ff5c3c8 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -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" -- 2.41.0