Correctly type videojs player
authorChocobozzz <me@florianbigard.com>
Tue, 28 Jan 2020 16:29:50 +0000 (17:29 +0100)
committerChocobozzz <me@florianbigard.com>
Wed, 29 Jan 2020 10:48:15 +0000 (11:48 +0100)
25 files changed:
client/package.json
client/src/assets/player/bezels/bezels-plugin.ts
client/src/assets/player/bezels/pause-bezel.ts [new file with mode: 0644]
client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts
client/src/assets/player/peertube-player-manager.ts
client/src/assets/player/peertube-plugin.ts
client/src/assets/player/peertube-videojs-typings.ts
client/src/assets/player/upnext/end-card.ts [new file with mode: 0644]
client/src/assets/player/upnext/upnext-plugin.ts
client/src/assets/player/videojs-components/next-video-button.ts
client/src/assets/player/videojs-components/p2p-info-button.ts
client/src/assets/player/videojs-components/peertube-link-button.ts
client/src/assets/player/videojs-components/peertube-load-progress-bar.ts
client/src/assets/player/videojs-components/resolution-menu-button.ts
client/src/assets/player/videojs-components/resolution-menu-item.ts
client/src/assets/player/videojs-components/settings-dialog.ts [new file with mode: 0644]
client/src/assets/player/videojs-components/settings-menu-button.ts
client/src/assets/player/videojs-components/settings-menu-item.ts
client/src/assets/player/videojs-components/settings-panel-child.ts [new file with mode: 0644]
client/src/assets/player/videojs-components/settings-panel.ts [new file with mode: 0644]
client/src/assets/player/videojs-components/theater-button.ts
client/src/assets/player/webtorrent/webtorrent-plugin.ts
client/tsconfig.json
client/webpack/webpack.video-embed.js
client/yarn.lock

index 7205dbe8f4f77f4fdf598dc65c491b63663d439e..e9c74787c53192dfb217aca2aab28968fb4c83db 100644 (file)
@@ -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",
     "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",
index c2c2519617846acfcdf730b00f2a6d6ac8981c7a..49917752622b45c458a4cd415fa3016221ea5a90 100644 (file)
@@ -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>
-  `
-}
-
-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()
-    })
-  }
+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 (file)
index 0000000..98eb120
--- /dev/null
@@ -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)
index c3f863f72eea1b046c21c6142bf0f1a0ef5fb5a9..512054ae63ab19f2b3c45a1e54850ca358c0b2cc 100644 (file)
@@ -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
index d9e02cd7da442f225a4c13a5a5cefda59e738c3b..4e6387a5383f364b9b413af14159ba02b0dabef6 100644 (file)
@@ -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: {
index 9824c43b54143b891937134b02fc908b2437bca8..19d1046769281f0738f084e9d0563b3574c41850 100644 (file)
@@ -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()) ?
index aad4dbb4faa6c7b7bd3dc3e0226116d1158922cd..7b0ea20748e36ee10c61d205bb9e1940ed14d442 100644 (file)
@@ -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 (file)
index 0000000..d121a83
--- /dev/null
@@ -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)
index a3747b25fae5e21c7c711a40aa39557fdc2bc64d..6512fec2c62687bceb540e242d302e3abf483aa3 100644 (file)
-// @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>
-  `
-}
-
-// @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<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')
index bf5c1aba4ebd272ba437a3a1225b3ca9baf24009..bdb245dccfdb8e2d43a82a184d9ea64f1949aee0 100644 (file)
@@ -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)
index 6424787b286513740d363544fae2fdcc3267f773..db6806fed121d9605c53647b6f5598164187666d 100644 (file)
@@ -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)
index 4d0ea37f5c1439fb7a8c4daa0eb207f62e83a24d..0db9762a5bdc179f6809546211dd55861500e1e6 100644 (file)
@@ -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)
index b594fc1c59ee52834e696c3bc83916dc531e3b2e..8168e8f2d2506c3440bd9b123944db19bffc24ab 100644 (file)
@@ -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) + '%'
   }
 
 }
index 86be03af761deb5a8b69587fc0cc520d1f595330..af044a9e5aeb78fd8c6656ddf1568b701e1c3f7f 100644 (file)
@@ -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)
index 6c42fefd2f1eb60109c9e4ada9ac44ac1ee413e3..b039c4572a88b5dad12bba9a28a625cb5619dd75 100644 (file)
@@ -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 }
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 (file)
index 0000000..dd0b1e4
--- /dev/null
@@ -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 }
index b700f4be6a23a5e20a185784274cec32aedb4042..eae628e7d05a2933c6ffb752794a8054e27409f0 100644 (file)
@@ -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 }
index 84d394c0ea46afed9d65a966bc180f20a57664d9..f5671f49d9be2a98e2fa69247afa8135c4701912 100644 (file)
@@ -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 (file)
index 0000000..d12e821
--- /dev/null
@@ -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 (file)
index 0000000..2090abf
--- /dev/null
@@ -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 }
index bf383cf34827392a98f1a2a41f844e82869e7907..1c8c9f154bdecb17bc886d4da54dec305b6348ca 100644 (file)
@@ -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)
index 35cf85c997f3b01060ab70a8cba3105313092ade..3d335acbc0adfb500440c5f288b8ffc1c778a85b 100644 (file)
@@ -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
     }
index 8824c4f7c977290433665cddacf75377a7e874b5..c4f2d6a6a5afd2fb03453f48bde6457ddcdc9cf9 100644 (file)
@@ -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" ],
index 909048cca866a318508db4f7a27b6e3b76d6e35f..f6d532556c70b6a86b6fc557576ca65b203343f6 100644 (file)
@@ -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')
       }
     },
 
index c900ae54971b3111e7bf4d26c6cc894b0d461b89..20ff5c3c8d52837713b1a5757a45e334b178ce35 100644 (file)
   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"