Correctly type videojs player

pull/2438/head
Chocobozzz 2020-01-28 17:29:50 +01:00
parent 0374b6b5cd
commit f5fcd9f725
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
25 changed files with 695 additions and 617 deletions

View 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",
@ -133,6 +133,7 @@
"videojs-dock": "^2.0.2",
"videojs-hotkeys": "^0.2.21",
"videostream": "~3.2.1",
"vtt.js": "^0.13.0",
"webpack-bundle-analyzer": "^3.0.2",
"webpack-cli": "^3.0.8",
"webtorrent": "^0.107.16",

View 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>
`
}
const Plugin = videojs.getPlugin('plugin')
function getPlayBezel () {
return `
<div class="vjs-bezels-play">
<div class="vjs-bezel" role="status" aria-label="Play">
<div class="vjs-bezel-icon">
<svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%">
<use class="vjs-svg-shadow" xlink:href="#vjs-id-2"></use>
<path class="vjs-svg-fill" d="M 12,26 18.5,22 18.5,14 12,10 z M 18.5,22 25,18 25,18 18.5,14 z" id="ytp-id-2"></path>
</svg>
</div>
</div>
</div>
`
}
// @ts-ignore-start
const Component = videojs.getComponent('Component')
class PauseBezel extends Component {
options_: any
container: HTMLBodyElement
constructor (player: videojs.Player, options: any) {
super(player, options)
this.options_ = options
player.on('pause', (_: any) => {
if (player.seeking() || player.ended()) return
this.container.innerHTML = getPauseBezel()
this.showBezel()
})
player.on('play', (_: any) => {
if (player.seeking()) return
this.container.innerHTML = getPlayBezel()
this.showBezel()
})
}
createEl () {
const container = super.createEl('div', {
className: 'vjs-bezels-content'
})
this.container = container
container.style.display = 'none'
return container
}
showBezel () {
this.container.style.display = 'inherit'
setTimeout(() => {
this.container.style.display = 'none'
}, 500) // matching the animation duration
}
}
// @ts-ignore-end
videojs.registerComponent('PauseBezel', PauseBezel)
const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin')
class BezelsPlugin extends Plugin {
constructor (player: videojs.Player, options: any = {}) {
super(player, options)
constructor (player: VideoJsPlayer, options?: videojs.ComponentOptions) {
super(player)
this.player.ready(() => {
player.addClass('vjs-bezels')
@ -90,4 +17,5 @@ class BezelsPlugin extends Plugin {
}
videojs.registerPlugin('bezels', BezelsPlugin)
export { BezelsPlugin }

View File

@ -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)

View 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

View 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/peertube-link-button'
import './videojs-components/resolution-menu-button'
import './videojs-components/settings-menu-button'
import './videojs-components/p2p-info-button'
import './videojs-components/peertube-link-button'
import './videojs-components/peertube-load-progress-bar'
import './videojs-components/resolution-menu-button'
import './videojs-components/resolution-menu-item'
import './videojs-components/settings-dialog'
import './videojs-components/settings-menu-button'
import './videojs-components/settings-menu-item'
import './videojs-components/settings-panel'
import './videojs-components/settings-panel-child'
import './videojs-components/theater-button'
import { P2PMediaLoaderPluginOptions, UserWatching, VideoJSCaption, VideoJSPluginOptions, videojsUntyped } from './peertube-videojs-typings'
import { P2PMediaLoaderPluginOptions, UserWatching, VideoJSCaption, VideoJSPluginOptions } from './peertube-videojs-typings'
import { buildVideoEmbed, buildVideoLink, copyToClipboard, getRtcConfig } from './utils'
import { isDefaultLocale } from '../../../../shared/models/i18n/i18n'
import { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
@ -24,12 +29,17 @@ import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
import { getStoredP2PEnabled } from './peertube-player-local-storage'
import { TranslationsManager } from './translations-manager'
// For VideoJS
(window as any).WebVTT = require('vtt.js/lib/vtt.js').WebVTT;
// Change 'Playback Rate' to 'Speed' (smaller for our settings menu)
videojsUntyped.getComponent('PlaybackRateMenuButton').prototype.controlText_ = 'Speed'
(videojs.getComponent('PlaybackRateMenuButton') as any).prototype.controlText_ = 'Speed'
const CaptionsButton = videojs.getComponent('CaptionsButton') as any
// Change Captions to Subtitles/CC
videojsUntyped.getComponent('CaptionsButton').prototype.controlText_ = 'Subtitles/CC'
CaptionsButton.prototype.controlText_ = 'Subtitles/CC'
// We just want to display 'Off' instead of 'captions off', keep a space so the variable == true (hacky I know)
videojsUntyped.getComponent('CaptionsButton').prototype.label_ = ' '
CaptionsButton.prototype.label_ = ' '
export type PlayerMode = 'webtorrent' | 'p2p-media-loader'
@ -92,9 +102,9 @@ export type PeertubePlayerManagerOptions = {
export class PeertubePlayerManager {
private static playerElementClassName: string
private static onPlayerChange: (player: any) => void
private static onPlayerChange: (player: VideoJsPlayer) => void
static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions, onPlayerChange: (player: any) => void) {
static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions, onPlayerChange: (player: VideoJsPlayer) => void) {
let p2pMediaLoader: any
this.onPlayerChange = onPlayerChange
@ -114,12 +124,12 @@ export class PeertubePlayerManager {
const self = this
return new Promise(res => {
videojs(options.common.playerElement, videojsOptions, function (this: any) {
videojs(options.common.playerElement, videojsOptions, function (this: VideoJsPlayer) {
const player = this
let alreadyFallback = false
player.tech_.one('error', () => {
player.tech(true).one('error', () => {
if (!alreadyFallback) self.maybeFallbackToWebTorrent(mode, player, options)
alreadyFallback = true
})
@ -164,7 +174,7 @@ export class PeertubePlayerManager {
const videojsOptions = this.getVideojsOptions(mode, options)
const self = this
videojs(newVideoElement, videojsOptions, function (this: any) {
videojs(newVideoElement, videojsOptions, function (this: VideoJsPlayer) {
const player = this
self.addContextMenu(mode, player, options.common.embedUrl)
@ -173,7 +183,11 @@ export class PeertubePlayerManager {
})
}
private static getVideojsOptions (mode: PlayerMode, options: PeertubePlayerManagerOptions, p2pMediaLoaderModule?: any) {
private static getVideojsOptions (
mode: PlayerMode,
options: PeertubePlayerManagerOptions,
p2pMediaLoaderModule?: any
): VideoJsPlayerOptions {
const commonOptions = options.common
let autoplay = commonOptions.autoplay
@ -213,7 +227,7 @@ export class PeertubePlayerManager {
html5,
// We don't use text track settings for now
textTrackSettings: false,
textTrackSettings: false as any, // FIXME: typings
controls: commonOptions.controls !== undefined ? commonOptions.controls : true,
loop: commonOptions.loop !== undefined ? commonOptions.loop : false,
@ -237,7 +251,7 @@ export class PeertubePlayerManager {
peertubeLink: commonOptions.peertubeLink,
theaterButton: commonOptions.theaterButton,
nextVideo: commonOptions.nextVideo
})
}) as any // FIXME: typings
}
}
@ -406,7 +420,7 @@ export class PeertubePlayerManager {
return children
}
private static addContextMenu (mode: PlayerMode, player: any, videoEmbedUrl: string) {
private static addContextMenu (mode: PlayerMode, player: VideoJsPlayer, videoEmbedUrl: string) {
const content = [
{
label: player.localize('Copy the video URL'),
@ -416,9 +430,8 @@ export class PeertubePlayerManager {
},
{
label: player.localize('Copy the video URL at the current time'),
listener: function () {
const player = this as videojs.Player
copyToClipboard(buildVideoLink({ startTime: player.currentTime() }))
listener: function (this: VideoJsPlayer) {
copyToClipboard(buildVideoLink({ startTime: this.currentTime() }))
}
},
{
@ -432,9 +445,8 @@ export class PeertubePlayerManager {
if (mode === 'webtorrent') {
content.push({
label: player.localize('Copy magnet URI'),
listener: function () {
const player = this as videojs.Player
copyToClipboard(player.webtorrent().getCurrentVideoFile().magnetUri)
listener: function (this: VideoJsPlayer) {
copyToClipboard(this.webtorrent().getCurrentVideoFile().magnetUri)
}
})
}
@ -472,7 +484,8 @@ export class PeertubePlayerManager {
return event.key === '>'
},
handler: function (player: videojs.Player) {
player.playbackRate((player.playbackRate() + 0.1).toFixed(2))
const newValue = Math.min(player.playbackRate() + 0.1, 5)
player.playbackRate(parseFloat(newValue.toFixed(2)))
}
},
decreasePlaybackRateKey: {
@ -480,7 +493,8 @@ export class PeertubePlayerManager {
return event.key === '<'
},
handler: function (player: videojs.Player) {
player.playbackRate((player.playbackRate() - 0.1).toFixed(2))
const newValue = Math.max(player.playbackRate() - 0.1, 0.10)
player.playbackRate(parseFloat(newValue.toFixed(2)))
}
},
frameByFrame: {

View 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()) ?

View 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,22 +6,46 @@ import { PlayerMode } from './peertube-player-manager'
import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
import { VideoFile } from '@shared/models'
declare namespace videojs {
interface Player {
declare module 'video.js' {
export interface VideoJsPlayer {
theaterEnabled: boolean
// FIXME: add it to upstream typings
posterImage: {
show (): void
hide (): void
}
handleTechSeeked_ (): void
// Plugins
peertube (): PeerTubePlugin
webtorrent (): WebTorrentPlugin
p2pMediaLoader (): P2pMediaLoaderPlugin
contextmenuUI (options: any): any
bezels (): void
qualityLevels (): { height: number, id: number }[] & {
selectedIndex: number
addQualityLevel (representation: {
id: number
label: string
height: number,
_enabled: boolean
}): void
}
textTracks (): TextTrackList & {
on: Function
tracks_: { kind: string, mode: string, language: string }[]
}
}
}
interface VideoJSComponentInterface {
_player: videojs.Player
new (player: videojs.Player, options?: any): any
registerComponent (name: string, obj: any): any
}
type VideoJSCaption = {
label: string
language: string
@ -78,9 +99,6 @@ type VideoJSPluginOptions = {
p2pMediaLoader?: P2PMediaLoaderPluginOptions
}
// videojs typings don't have some method we need
const videojsUntyped = videojs as any
type LoadedQualityData = {
qualitySwitchCallback: Function,
qualityData: {
@ -123,8 +141,6 @@ export {
PlayerNetworkInfo,
ResolutionUpdateData,
AutoResolutionUpdateData,
VideoJSComponentInterface,
videojsUntyped,
VideoJSCaption,
UserWatching,
PeerTubePluginOptions,

View File

@ -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)

View File

@ -1,154 +1,11 @@
// @ts-ignore
import * as videojs from 'video.js'
import { VideoJSComponentInterface } from '../peertube-videojs-typings'
import videojs, { VideoJsPlayer } from 'video.js'
import { EndCardOptions } from './end-card'
function getMainTemplate (options: any) {
return `
<div class="vjs-upnext-top">
<span class="vjs-upnext-headtext">${options.headText}</span>
<div class="vjs-upnext-title"></div>
</div>
<div class="vjs-upnext-autoplay-icon">
<svg height="100%" version="1.1" viewbox="0 0 98 98" width="100%">
<circle class="vjs-upnext-svg-autoplay-circle" cx="49" cy="49" fill="#000" fill-opacity="0.8" r="48"></circle>
<circle class="vjs-upnext-svg-autoplay-ring" cx="-49" cy="49" fill-opacity="0" r="46.5" stroke="#FFFFFF" stroke-width="4" transform="rotate(-90)"></circle>
<polygon class="vjs-upnext-svg-autoplay-triangle" fill="#fff" points="32,27 72,49 32,71"></polygon></svg>
</div>
<span class="vjs-upnext-bottom">
<span class="vjs-upnext-cancel">
<button class="vjs-upnext-cancel-button" tabindex="0" aria-label="Cancel autoplay">${options.cancelText}</button>
</span>
<span class="vjs-upnext-suspended">${options.suspendedText}</span>
</span>
`
}
const Plugin = videojs.getPlugin('plugin')
// @ts-ignore-start
const Component = videojs.getComponent('Component')
class EndCard extends Component {
options_: any
dashOffsetTotal = 586
dashOffsetStart = 293
interval = 50
upNextEvents = new videojs.EventTarget()
ticks = 0
totalTicks: number
container: HTMLElement
title: HTMLElement
autoplayRing: HTMLElement
cancelButton: HTMLElement
suspendedMessage: HTMLElement
nextButton: HTMLElement
constructor (player: videojs.Player, options: any) {
super(player, options)
this.totalTicks = this.options_.timeout / this.interval
player.on('ended', (_: any) => {
if (!this.options_.condition()) return
player.addClass('vjs-upnext--showing')
this.showCard((canceled: boolean) => {
player.removeClass('vjs-upnext--showing')
this.container.style.display = 'none'
if (!canceled) {
this.options_.next()
}
})
})
player.on('playing', () => {
this.upNextEvents.trigger('playing')
})
}
createEl () {
const container = super.createEl('div', {
className: 'vjs-upnext-content',
innerHTML: getMainTemplate(this.options_)
})
this.container = container
container.style.display = 'none'
this.autoplayRing = container.getElementsByClassName('vjs-upnext-svg-autoplay-ring')[0]
this.title = container.getElementsByClassName('vjs-upnext-title')[0]
this.cancelButton = container.getElementsByClassName('vjs-upnext-cancel-button')[0]
this.suspendedMessage = container.getElementsByClassName('vjs-upnext-suspended')[0]
this.nextButton = container.getElementsByClassName('vjs-upnext-autoplay-icon')[0]
this.cancelButton.onclick = () => {
this.upNextEvents.trigger('cancel')
}
this.nextButton.onclick = () => {
this.upNextEvents.trigger('next')
}
return container
}
showCard (cb: Function) {
let timeout: any
this.autoplayRing.setAttribute('stroke-dasharray', '' + this.dashOffsetStart)
this.autoplayRing.setAttribute('stroke-dashoffset', '' + -this.dashOffsetStart)
this.title.innerHTML = this.options_.getTitle()
this.upNextEvents.one('cancel', () => {
clearTimeout(timeout)
cb(true)
})
this.upNextEvents.one('playing', () => {
clearTimeout(timeout)
cb(true)
})
this.upNextEvents.one('next', () => {
clearTimeout(timeout)
cb(false)
})
const goToPercent = (percent: number) => {
const newOffset = Math.max(-this.dashOffsetTotal, - this.dashOffsetStart - percent * this.dashOffsetTotal / 2 / 100)
this.autoplayRing.setAttribute('stroke-dashoffset', '' + newOffset)
}
const tick = () => {
goToPercent((this.ticks++) * 100 / this.totalTicks)
}
const update = () => {
if (this.options_.suspended()) {
this.suspendedMessage.innerText = this.options_.suspendedText
goToPercent(0)
this.ticks = 0
timeout = setTimeout(update.bind(this), 300) // checks once supsended can be a bit longer
} else if (this.ticks >= this.totalTicks) {
clearTimeout(timeout)
cb(false)
} else {
this.suspendedMessage.innerText = ''
tick()
timeout = setTimeout(update.bind(this), this.interval)
}
}
this.container.style.display = 'block'
timeout = setTimeout(update.bind(this), this.interval)
}
}
// @ts-ignore-end
videojs.registerComponent('EndCard', EndCard)
const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin')
class UpNextPlugin extends Plugin {
constructor (player: videojs.Player, options: any = {}) {
constructor (player: VideoJsPlayer, options: Partial<EndCardOptions> = {}) {
const settings = {
next: options.next,
getTitle: options.getTitle,
@ -160,7 +17,7 @@ class UpNextPlugin extends Plugin {
suspended: options.suspended
}
super(player, settings)
super(player)
this.player.ready(() => {
player.addClass('vjs-upnext')

View 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)

View 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)

View 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)

View 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) + '%'
}
}

View 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)

View 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 }

View File

@ -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 }

View 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'
const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button')
const Menu: VideoJSComponentInterface = videojsUntyped.getComponent('Menu')
const Component: VideoJSComponentInterface = videojsUntyped.getComponent('Component')
import { SettingsDialog } from './settings-dialog'
import { SettingsPanel } from './settings-panel'
import { SettingsPanelChild } from './settings-panel-child'
const Button = videojs.getComponent('Button')
const Menu = videojs.getComponent('Menu')
const Component = videojs.getComponent('Component')
export interface SettingsButtonOptions extends videojs.ComponentOptions {
entries: any[]
setup?: {
maxHeightOffset: number
}
}
class SettingsButton extends Button {
playerComponent = videojs.Player
dialog: any
dialogEl: any
menu: any
panel: any
panelChild: any
dialog: SettingsDialog
dialogEl: HTMLElement
menu: videojs.Menu
panel: SettingsPanel
panelChild: SettingsPanelChild
addSettingsItemHandler: Function
disposeSettingsItemHandler: Function
playerClickHandler: Function
userInactiveHandler: Function
addSettingsItemHandler: typeof SettingsButton.prototype.onAddSettingsItem
disposeSettingsItemHandler: typeof SettingsButton.prototype.onDisposeSettingsItem
playerClickHandler: typeof SettingsButton.prototype.onPlayerClick
userInactiveHandler: typeof SettingsButton.prototype.onUserInactive
constructor (player: videojs.Player, options: any) {
private settingsButtonOptions: SettingsButtonOptions
constructor (player: VideoJsPlayer, options?: SettingsButtonOptions) {
super(player, options)
this.playerComponent = player
this.dialog = this.playerComponent.addChild('settingsDialog')
this.dialogEl = this.dialog.el_
this.settingsButtonOptions = options
this.controlText('Settings')
this.dialog = this.player().addChild('settingsDialog')
this.dialogEl = this.dialog.el() as HTMLElement
this.menu = null
this.panel = this.dialog.addChild('settingsPanel')
this.panelChild = this.panel.addChild('settingsPanelChild')
this.addClass('vjs-settings')
this.el_.setAttribute('aria-label', 'Settings Button')
this.el().setAttribute('aria-label', 'Settings Button')
// Event handlers
this.addSettingsItemHandler = this.onAddSettingsItem.bind(this)
@ -84,7 +93,7 @@ class SettingsButton extends Button {
this.hideDialog()
if (this.options_.entries.length === 0) {
if (this.settingsButtonOptions.entries.length === 0) {
this.addClass('vjs-hidden')
}
}
@ -103,10 +112,10 @@ class SettingsButton extends Button {
}
bindEvents () {
this.playerComponent.on('click', this.playerClickHandler)
this.playerComponent.on('addsettingsitem', this.addSettingsItemHandler)
this.playerComponent.on('disposesettingsitem', this.disposeSettingsItemHandler)
this.playerComponent.on('userinactive', this.userInactiveHandler)
this.player().on('click', this.playerClickHandler)
this.player().on('addsettingsitem', this.addSettingsItemHandler)
this.player().on('disposesettingsitem', this.disposeSettingsItemHandler)
this.player().on('userinactive', this.userInactiveHandler)
}
buildCSSClass () {
@ -122,9 +131,9 @@ class SettingsButton extends Button {
}
showDialog () {
this.player_.peertube().onMenuOpen()
this.player().peertube().onMenuOpen();
this.menu.el_.style.opacity = '1'
(this.menu.el() as HTMLElement).style.opacity = '1'
this.dialog.show()
this.setDialogSize(this.getComponentSize(this.menu))
@ -134,23 +143,24 @@ class SettingsButton extends Button {
this.player_.peertube().onMenuClosed()
this.dialog.hide()
this.setDialogSize(this.getComponentSize(this.menu))
this.menu.el_.style.opacity = '1'
this.setDialogSize(this.getComponentSize(this.menu));
(this.menu.el() as HTMLElement).style.opacity = '1'
this.resetChildren()
}
getComponentSize (element: any) {
getComponentSize (element: videojs.Component | HTMLElement) {
let width: number = null
let height: number = null
// Could be component or just DOM element
if (element instanceof Component) {
width = element.el_.offsetWidth
height = element.el_.offsetHeight
const el = element.el() as HTMLElement
// keep width/height as properties for direct use
element.width = width
element.height = height
width = el.offsetWidth
height = el.offsetHeight;
(element as any).width = width;
(element as any).height = height
} else {
width = element.offsetWidth
height = element.offsetHeight
@ -164,15 +174,17 @@ class SettingsButton extends Button {
return
}
const offset = this.options_.setup.maxHeightOffset
const maxHeight = this.playerComponent.el_.offsetHeight - offset
const offset = this.settingsButtonOptions.setup.maxHeightOffset
const maxHeight = (this.player().el() as HTMLElement).offsetHeight - offset // FIXME: typings
const panelEl = this.panel.el() as HTMLElement
if (height > maxHeight) {
height = maxHeight
width += 17
this.panel.el_.style.maxHeight = `${height}px`
} else if (this.panel.el_.style.maxHeight !== '') {
this.panel.el_.style.maxHeight = ''
panelEl.style.maxHeight = `${height}px`
} else if (panelEl.style.maxHeight !== '') {
panelEl.style.maxHeight = ''
}
this.dialogEl.style.width = `${width}px`
@ -182,7 +194,7 @@ class SettingsButton extends Button {
buildMenu () {
this.menu = new Menu(this.player())
this.menu.addClass('vjs-main-menu')
const entries = this.options_.entries
const entries = this.settingsButtonOptions.entries
if (entries.length === 0) {
this.addClass('vjs-hidden')
@ -191,7 +203,7 @@ class SettingsButton extends Button {
}
for (const entry of entries) {
this.addMenuItem(entry, this.options_)
this.addMenuItem(entry, this.settingsButtonOptions)
}
this.panelChild.addChild(this.menu)
@ -199,15 +211,17 @@ class SettingsButton extends Button {
addMenuItem (entry: any, options: any) {
const openSubMenu = function (this: any) {
if (videojsUntyped.dom.hasClass(this.el_, 'open')) {
videojsUntyped.dom.removeClass(this.el_, 'open')
if (videojs.dom.hasClass(this.el_, 'open')) {
videojs.dom.removeClass(this.el_, 'open')
} else {
videojsUntyped.dom.addClass(this.el_, 'open')
videojs.dom.addClass(this.el_, 'open')
}
}
options.name = toTitleCase(entry)
const settingsMenuItem = new SettingsMenuItem(this.player(), options, entry, this as any)
const newOptions = Object.assign({}, options, { entry, menuButton: this })
const settingsMenuItem = new SettingsMenuItem(this.player(), newOptions)
this.menu.addChild(settingsMenuItem)
@ -221,7 +235,7 @@ class SettingsButton extends Button {
resetChildren () {
for (const menuChild of this.menu.children()) {
menuChild.reset()
(menuChild as SettingsMenuItem).reset()
}
}
@ -230,75 +244,12 @@ class SettingsButton extends Button {
*/
hideChildren () {
for (const menuChild of this.menu.children()) {
menuChild.hideSubMenu()
(menuChild as SettingsMenuItem).hideSubMenu()
}
}
}
class SettingsPanel extends Component {
constructor (player: videojs.Player, options: any) {
super(player, options)
}
createEl () {
return super.createEl('div', {
className: 'vjs-settings-panel',
innerHTML: '',
tabIndex: -1
})
}
}
class SettingsPanelChild extends Component {
constructor (player: videojs.Player, options: any) {
super(player, options)
}
createEl () {
return super.createEl('div', {
className: 'vjs-settings-panel-child',
innerHTML: '',
tabIndex: -1
})
}
}
class SettingsDialog extends Component {
constructor (player: videojs.Player, options: any) {
super(player, options)
this.hide()
}
/**
* Create the component's DOM element
*
* @return {Element}
* @method createEl
*/
createEl () {
const uniqueId = this.id_
const dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId
const dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId
return super.createEl('div', {
className: 'vjs-settings-dialog vjs-modal-overlay',
innerHTML: '',
tabIndex: -1
}, {
'role': 'dialog',
'aria-labelledby': dialogLabelId,
'aria-describedby': dialogDescriptionId
})
}
}
SettingsButton.prototype.controlText_ = 'Settings'
Component.registerComponent('SettingsButton', SettingsButton)
Component.registerComponent('SettingsDialog', SettingsDialog)
Component.registerComponent('SettingsPanel', SettingsPanel)
Component.registerComponent('SettingsPanelChild', SettingsPanelChild)
export { SettingsButton, SettingsDialog, SettingsPanel, SettingsPanelChild }
export { SettingsButton }

View 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'
import videojs, { VideoJsPlayer } from 'video.js'
import { SettingsButton } from './settings-menu-button'
import { SettingsDialog } from './settings-dialog'
import { SettingsPanel } from './settings-panel'
import { SettingsPanelChild } from './settings-panel-child'
const MenuItem: VideoJSComponentInterface = videojsUntyped.getComponent('MenuItem')
const component: VideoJSComponentInterface = videojsUntyped.getComponent('Component')
const MenuItem = videojs.getComponent('MenuItem')
const component = videojs.getComponent('Component')
export interface SettingsMenuItemOptions extends videojs.MenuItemOptions {
entry: string
menuButton: SettingsButton
}
class SettingsMenuItem extends MenuItem {
settingsButton: any
dialog: any
mainMenu: any
panel: any
panelChild: any
panelChildEl: any
size: any
settingsButton: SettingsButton
dialog: SettingsDialog
mainMenu: videojs.Menu
panel: SettingsPanel
panelChild: SettingsPanelChild
panelChildEl: HTMLElement
size: number[]
menuToLoad: string
subMenu: any
subMenu: SettingsButton
submenuClickHandler: Function
transitionEndHandler: Function
submenuClickHandler: typeof SettingsMenuItem.prototype.onSubmenuClick
transitionEndHandler: typeof SettingsMenuItem.prototype.onTransitionEnd
settingsSubMenuTitleEl_: any
settingsSubMenuValueEl_: any
settingsSubMenuEl_: any
settingsSubMenuTitleEl_: HTMLElement
settingsSubMenuValueEl_: HTMLElement
settingsSubMenuEl_: HTMLElement
constructor (player: videojs.Player, options: any, entry: string, menuButton: VideoJSComponentInterface) {
constructor (player: VideoJsPlayer, options?: SettingsMenuItemOptions) {
super(player, options)
this.settingsButton = menuButton
this.settingsButton = options.menuButton
this.dialog = this.settingsButton.dialog
this.mainMenu = this.settingsButton.menu
this.panel = this.dialog.getChild('settingsPanel')
this.panelChild = this.panel.getChild('settingsPanelChild')
this.panelChildEl = this.panelChild.el_
this.panelChildEl = this.panelChild.el() as HTMLElement
this.size = null
// keep state of what menu type is loading next
this.menuToLoad = 'mainmenu'
const subMenuName = toTitleCase(entry)
const SubMenuComponent = videojsUntyped.getComponent(subMenuName)
const subMenuName = toTitleCase(options.entry)
const SubMenuComponent = videojs.getComponent(subMenuName)
if (!SubMenuComponent) {
throw new Error(`Component ${subMenuName} does not exist`)
}
this.subMenu = new SubMenuComponent(this.player(), options, menuButton, this)
const subMenuClass = this.subMenu.buildCSSClass().split(' ')[0]
const newOptions = Object.assign({}, options, { entry: options.menuButton, menuButton: this })
this.subMenu = new SubMenuComponent(this.player(), newOptions) as any // FIXME: typings
const subMenuClass = this.subMenu.buildCSSClass().split(' ')[ 0 ]
this.settingsSubMenuEl_.className += ' ' + subMenuClass
this.eventHandlers()
@ -72,7 +78,7 @@ class SettingsMenuItem extends MenuItem {
player.on('captionsChanged', () => {
setTimeout(() => {
this.settingsSubMenuEl_.innerHTML = ''
this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_)
this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el())
this.update()
this.bindClickEvents()
}, 0)
@ -119,27 +125,27 @@ class SettingsMenuItem extends MenuItem {
* @method createEl
*/
createEl () {
const el = videojsUntyped.dom.createEl('li', {
const el = videojs.dom.createEl('li', {
className: 'vjs-menu-item'
})
this.settingsSubMenuTitleEl_ = videojsUntyped.dom.createEl('div', {
this.settingsSubMenuTitleEl_ = videojs.dom.createEl('div', {
className: 'vjs-settings-sub-menu-title'
})
}) as HTMLElement
el.appendChild(this.settingsSubMenuTitleEl_)
this.settingsSubMenuValueEl_ = videojsUntyped.dom.createEl('div', {
this.settingsSubMenuValueEl_ = videojs.dom.createEl('div', {
className: 'vjs-settings-sub-menu-value'
})
}) as HTMLElement
el.appendChild(this.settingsSubMenuValueEl_)
this.settingsSubMenuEl_ = videojsUntyped.dom.createEl('div', {
this.settingsSubMenuEl_ = videojs.dom.createEl('div', {
className: 'vjs-settings-sub-menu'
})
}) as HTMLElement
return el
return el as HTMLLIElement
}
/**
@ -147,17 +153,17 @@ class SettingsMenuItem extends MenuItem {
*
* @method handleClick
*/
handleClick () {
handleClick (event: videojs.EventTarget.Event) {
this.menuToLoad = 'submenu'
// Remove open class to ensure only the open submenu gets this class
videojsUntyped.dom.removeClass(this.el_, 'open')
videojs.dom.removeClass(this.el(), 'open')
super.handleClick()
super.handleClick(event);
this.mainMenu.el_.style.opacity = '0'
(this.mainMenu.el() as HTMLElement).style.opacity = '0'
// Whether to add or remove vjs-hidden class on the settingsSubMenuEl element
if (videojsUntyped.dom.hasClass(this.settingsSubMenuEl_, 'vjs-hidden')) {
videojsUntyped.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
if (videojs.dom.hasClass(this.settingsSubMenuEl_, 'vjs-hidden')) {
videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
// animation not played without timeout
setTimeout(() => {
@ -167,7 +173,7 @@ class SettingsMenuItem extends MenuItem {
this.settingsButton.setDialogSize(this.size)
} else {
videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
}
}
@ -178,9 +184,9 @@ class SettingsMenuItem extends MenuItem {
*/
createBackButton () {
const button = this.subMenu.menu.addChild('MenuItem', {}, 0)
button.name_ = 'BackButton'
button.addClass('vjs-back-button')
button.el_.innerHTML = this.player_.localize(this.subMenu.controlText_)
button.addClass('vjs-back-button');
(button.el() as HTMLElement).innerHTML = this.player().localize(this.subMenu.controlText())
}
/**
@ -189,17 +195,17 @@ class SettingsMenuItem extends MenuItem {
* @method PrefixedEvent
*/
PrefixedEvent (element: any, type: any, callback: any, action = 'addEvent') {
const prefix = ['webkit', 'moz', 'MS', 'o', '']
const prefix = [ 'webkit', 'moz', 'MS', 'o', '' ]
for (let p = 0; p < prefix.length; p++) {
if (!prefix[p]) {
if (!prefix[ p ]) {
type = type.toLowerCase()
}
if (action === 'addEvent') {
element.addEventListener(prefix[p] + type, callback, false)
element.addEventListener(prefix[ p ] + type, callback, false)
} else if (action === 'removeEvent') {
element.removeEventListener(prefix[p] + type, callback, false)
element.removeEventListener(prefix[ p ] + type, callback, false)
}
}
}
@ -211,7 +217,7 @@ class SettingsMenuItem extends MenuItem {
if (this.menuToLoad === 'mainmenu') {
// hide submenu
videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
// reset opacity to 0
this.settingsSubMenuEl_.style.opacity = '0'
@ -219,25 +225,27 @@ class SettingsMenuItem extends MenuItem {
}
reset () {
videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
this.settingsSubMenuEl_.style.opacity = '0'
this.setMargin()
}
loadMainMenu () {
const mainMenuEl = this.mainMenu.el() as HTMLElement
this.menuToLoad = 'mainmenu'
this.mainMenu.show()
this.mainMenu.el_.style.opacity = '0'
mainMenuEl.style.opacity = '0'
// back button will always take you to main menu, so set dialog sizes
this.settingsButton.setDialogSize([this.mainMenu.width, this.mainMenu.height])
const mainMenuAny = this.mainMenu as any
this.settingsButton.setDialogSize([ mainMenuAny.width, mainMenuAny.height ])
// animation not triggered without timeout (some async stuff ?!?)
setTimeout(() => {
// animate margin and opacity before hiding the submenu
// this triggers CSS Transition event
this.setMargin()
this.mainMenu.el_.style.opacity = '1'
mainMenuEl.style.opacity = '1'
}, 0)
}
@ -251,8 +259,8 @@ class SettingsMenuItem extends MenuItem {
this.update()
})
this.settingsSubMenuTitleEl_.innerHTML = this.player_.localize(this.subMenu.controlText_)
this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_)
this.settingsSubMenuTitleEl_.innerHTML = this.player().localize(this.subMenu.controlText())
this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el())
this.panelChildEl.appendChild(this.settingsSubMenuEl_)
this.update()
@ -283,7 +291,8 @@ class SettingsMenuItem extends MenuItem {
// or sets options_['selected'] on the selected playback rate.
// Thus we get the submenu value based on the labelEl of playbackRateMenuButton
if (subMenu === 'PlaybackRateMenuButton') {
setTimeout(() => this.settingsSubMenuValueEl_.innerHTML = this.subMenu.labelEl_.innerHTML, 250)
const html = (this.subMenu as any).labelEl_.innerHTML
setTimeout(() => this.settingsSubMenuValueEl_.innerHTML = html, 250)
} else {
// Loop trough the submenu items to find the selected child
for (const subMenuItem of this.subMenu.menu.children_) {
@ -292,13 +301,15 @@ class SettingsMenuItem extends MenuItem {
}
if (subMenuItem.hasClass('vjs-selected')) {
const subMenuItemUntyped = subMenuItem as any
// Prefer to use the function
if (typeof subMenuItem.getLabel === 'function') {
this.settingsSubMenuValueEl_.innerHTML = subMenuItem.getLabel()
if (typeof subMenuItemUntyped.getLabel === 'function') {
this.settingsSubMenuValueEl_.innerHTML = subMenuItemUntyped.getLabel()
break
}
this.settingsSubMenuValueEl_.innerHTML = subMenuItem.options_.label
this.settingsSubMenuValueEl_.innerHTML = subMenuItemUntyped.options_.label
}
}
}
@ -313,7 +324,7 @@ class SettingsMenuItem extends MenuItem {
if (!(item instanceof component)) {
continue
}
item.on(['tap', 'click'], this.submenuClickHandler)
item.on([ 'tap', 'click' ], this.submenuClickHandler)
}
}
@ -321,11 +332,11 @@ class SettingsMenuItem extends MenuItem {
// if number of submenu items change dynamically more logic will be needed
setSize () {
this.dialog.removeClass('vjs-hidden')
videojsUntyped.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
this.size = this.settingsButton.getComponentSize(this.settingsSubMenuEl_)
this.setMargin()
this.dialog.addClass('vjs-hidden')
videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
}
setMargin () {
@ -341,19 +352,19 @@ class SettingsMenuItem extends MenuItem {
*/
hideSubMenu () {
// after removing settings item this.el_ === null
if (!this.el_) {
if (!this.el()) {
return
}
if (videojsUntyped.dom.hasClass(this.el_, 'open')) {
videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
videojsUntyped.dom.removeClass(this.el_, 'open')
if (videojs.dom.hasClass(this.el(), 'open')) {
videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
videojs.dom.removeClass(this.el(), 'open')
}
}
}
SettingsMenuItem.prototype.contentElType = 'button'
videojsUntyped.registerComponent('SettingsMenuItem', SettingsMenuItem)
(SettingsMenuItem as any).prototype.contentElType = 'button'
videojs.registerComponent('SettingsMenuItem', SettingsMenuItem)
export { SettingsMenuItem }

View File

@ -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 }

View File

@ -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 }

View 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)

View 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
}

View 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" ],

View 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')
}
},

View File

@ -1157,10 +1157,10 @@
resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==
"@types/video.js@^7.2.5":
version "7.2.15"
resolved "https://registry.yarnpkg.com/@types/video.js/-/video.js-7.2.15.tgz#03d950f01c985a5082ead4d1b73064455a1c8c6f"
integrity sha512-NsojVfvTwdVqDe0+vJaoHOO2iuLm0sp/u8jEsZeLGsM3gNfg5WIOFd6NC0cQR9JHUuDPPSPF70jxdklGWm5jhQ==
"@types/video.js@^7.3.3":
version "7.3.3"
resolved "https://registry.yarnpkg.com/@types/video.js/-/video.js-7.3.3.tgz#b6870d954473dfd694e10b55a90c0f3be8522da3"
integrity sha512-yAb46+4A0dKFxOQRVLoLyfC/S/BmHLE10MxPXt/t88+7R4GWLHosHelVtYpKBRykjptdkqfQXNRXoQzDeKm6MA==
"@types/webpack-sources@^0.1.5":
version "0.1.5"
@ -10842,6 +10842,11 @@ void-elements@^2.0.0:
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
vtt.js@^0.13.0:
version "0.13.0"
resolved "https://registry.yarnpkg.com/vtt.js/-/vtt.js-0.13.0.tgz#955c667b34d5325b2012cb9e8ba9bad6e0b11ff8"
integrity sha1-lVxmezTVMlsgEsuei6m61uCxH/g=
watchpack@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"