"@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",
-// @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')
}
videojs.registerPlugin('bezels', BezelsPlugin)
+
export { BezelsPlugin }
--- /dev/null
+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)
-// 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'
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 = {
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)
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
})
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
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'
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'
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
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
})
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)
})
}
- 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
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,
peertubeLink: commonOptions.peertubeLink,
theaterButton: commonOptions.theaterButton,
nextVideo: commonOptions.nextVideo
- })
+ }) as any // FIXME: typings
}
}
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'),
},
{
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() }))
}
},
{
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)
}
})
}
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: {
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: {
-// 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 {
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
USER_WATCHING_VIDEO_INTERVAL: 5000 // Every 5 seconds, notify the user is watching the video
}
- private player: any
private videoCaptions: VideoJSCaption[]
private defaultSubtitle: string
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
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)
}
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'
})
// 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()) ?
-// 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'
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 = {
p2pMediaLoader?: P2PMediaLoaderPluginOptions
}
-// videojs typings don't have some method we need
-const videojsUntyped = videojs as any
-
type LoadedQualityData = {
qualitySwitchCallback: Function,
qualityData: {
PlayerNetworkInfo,
ResolutionUpdateData,
AutoResolutionUpdateData,
- VideoJSComponentInterface,
- videojsUntyped,
VideoJSCaption,
UserWatching,
PeerTubePluginOptions,
--- /dev/null
+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)
-// @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,
suspended: options.suspended
}
- super(player, settings)
+ super(player)
this.player.ready(() => {
player.addClass('vjs-upnext')
-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)
}
handleClick () {
- this.options_.handler()
+ this.nextVideoButtonOptions.handler()
}
-
}
-NextVideoButton.prototype.controlText_ = 'Next video'
-
-NextVideoButton.registerComponent('NextVideoButton', NextVideoButton)
+videojs.registerComponent('NextVideoButton', NextVideoButton)
-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'
})
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 ]
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)
-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)
}
}
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)
-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)
}
}
dispose () {
- this.partEls_ = null
-
super.dispose()
}
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) + '%'
}
}
-// 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))
}
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_)
for (const child of children) {
if (component !== child) {
- child.selected(false)
+ (child as videojs.MenuItem).selected(false)
}
}
})
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(
this.trigger('menuChanged')
}
}
-ResolutionMenuButton.prototype.controlText_ = 'Quality'
-MenuButton.registerComponent('ResolutionMenuButton', ResolutionMenuButton)
+videojs.registerComponent('ResolutionMenuButton', ResolutionMenuButton)
-// 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 }
private autoResolutionPossible: boolean
private currentResolutionLabel: string
- constructor (player: Player, options: any) {
+ constructor (player: VideoJsPlayer, options?: ResolutionMenuItemOptions) {
options.selectable = true
super(player, options)
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) {
}
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 }
--- /dev/null
+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 }
-// 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)
this.hideDialog()
- if (this.options_.entries.length === 0) {
+ if (this.settingsButtonOptions.entries.length === 0) {
this.addClass('vjs-hidden')
}
}
}
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 () {
}
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))
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
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`
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')
}
for (const entry of entries) {
- this.addMenuItem(entry, this.options_)
+ this.addMenuItem(entry, this.settingsButtonOptions)
}
this.panelChild.addChild(this.menu)
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)
resetChildren () {
for (const menuChild of this.menu.children()) {
- menuChild.reset()
+ (menuChild as SettingsMenuItem).reset()
}
}
*/
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 }
-// 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()
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)
* @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
}
/**
*
* @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(() => {
this.settingsButton.setDialogSize(this.size)
} else {
- videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
+ videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
}
}
*/
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())
}
/**
* @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)
}
}
}
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'
}
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)
}
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()
// 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_) {
}
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
}
}
}
if (!(item instanceof component)) {
continue
}
- item.on(['tap', 'click'], this.submenuClickHandler)
+ item.on([ 'tap', 'click' ], this.submenuClickHandler)
}
}
// 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 () {
*/
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 }
--- /dev/null
+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 }
--- /dev/null
+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 }
-// 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 () {
}
}
-TheaterButton.prototype.controlText_ = 'Theater mode'
-
-TheaterButton.registerComponent('TheaterButton', TheaterButton)
+videojs.registerComponent('TheaterButton', TheaterButton)
-// 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'
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 = {
dht: false
})
- private player: any
private currentVideoFile: VideoFile
private torrent: WebTorrent.Torrent
private downloadSpeeds: number[] = []
- constructor (player: videojs.Player, options: WebtorrentPluginOptions) {
- super(player, options)
+ constructor (player: VideoJsPlayer, options?: WebtorrentPluginOptions) {
+ super(player)
this.startTime = timeToInt(options.startTime)
}
// 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
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
}
// 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
video: qualityLevelsPayload
}
}
- this.player.tech_.trigger('loadedqualitydata', payload)
+ this.player.tech(true).trigger('loadedqualitydata', payload)
}
private buildQualityLabel (file: VideoFile) {
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
}
"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" ],
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')
}
},
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"
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"