187 lines
5.1 KiB
TypeScript
187 lines
5.1 KiB
TypeScript
|
import debug from 'debug'
|
||
|
import videojs from 'video.js'
|
||
|
import { logger } from '@root-helpers/logger'
|
||
|
import { addQueryParams } from '@shared/core-utils'
|
||
|
import { VideoFile } from '@shared/models'
|
||
|
import { PeerTubeResolution, PlayerNetworkInfo, WebVideoPluginOptions } from '../../types'
|
||
|
|
||
|
const debugLogger = debug('peertube:player:web-video-plugin')
|
||
|
|
||
|
const Plugin = videojs.getPlugin('plugin')
|
||
|
|
||
|
class WebVideoPlugin extends Plugin {
|
||
|
private readonly videoFiles: VideoFile[]
|
||
|
|
||
|
private currentVideoFile: VideoFile
|
||
|
private videoFileToken: () => string
|
||
|
|
||
|
private networkInfoInterval: any
|
||
|
|
||
|
private onErrorHandler: () => void
|
||
|
private onPlayHandler: () => void
|
||
|
|
||
|
constructor (player: videojs.Player, options?: WebVideoPluginOptions) {
|
||
|
super(player, options)
|
||
|
|
||
|
this.videoFiles = options.videoFiles
|
||
|
this.videoFileToken = options.videoFileToken
|
||
|
|
||
|
this.updateVideoFile({ videoFile: this.pickAverageVideoFile(), isUserResolutionChange: false })
|
||
|
|
||
|
player.ready(() => {
|
||
|
this.buildQualities()
|
||
|
|
||
|
this.setupNetworkInfoInterval()
|
||
|
|
||
|
if (this.videoFiles.length === 0) {
|
||
|
this.player.addClass('disabled')
|
||
|
return
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
dispose () {
|
||
|
clearInterval(this.networkInfoInterval)
|
||
|
|
||
|
if (this.onErrorHandler) this.player.off('error', this.onErrorHandler)
|
||
|
if (this.onPlayHandler) this.player.off('canplay', this.onPlayHandler)
|
||
|
|
||
|
super.dispose()
|
||
|
}
|
||
|
|
||
|
getCurrentResolutionId () {
|
||
|
return this.currentVideoFile.resolution.id
|
||
|
}
|
||
|
|
||
|
updateVideoFile (options: {
|
||
|
videoFile: VideoFile
|
||
|
isUserResolutionChange: boolean
|
||
|
}) {
|
||
|
this.currentVideoFile = options.videoFile
|
||
|
|
||
|
debugLogger('Updating web video file to ' + this.currentVideoFile.fileUrl)
|
||
|
|
||
|
const paused = this.player.paused()
|
||
|
const playbackRate = this.player.playbackRate()
|
||
|
const currentTime = this.player.currentTime()
|
||
|
|
||
|
// Enable error display now this is our last fallback
|
||
|
this.onErrorHandler = () => this.player.peertube().displayFatalError()
|
||
|
this.player.one('error', this.onErrorHandler)
|
||
|
|
||
|
let httpUrl = this.currentVideoFile.fileUrl
|
||
|
|
||
|
if (this.videoFileToken()) {
|
||
|
httpUrl = addQueryParams(httpUrl, { videoFileToken: this.videoFileToken() })
|
||
|
}
|
||
|
|
||
|
const oldAutoplayValue = this.player.autoplay()
|
||
|
if (options.isUserResolutionChange) {
|
||
|
this.player.autoplay(false)
|
||
|
this.player.addClass('vjs-updating-resolution')
|
||
|
}
|
||
|
|
||
|
this.player.src(httpUrl)
|
||
|
|
||
|
this.onPlayHandler = () => {
|
||
|
this.player.playbackRate(playbackRate)
|
||
|
this.player.currentTime(currentTime)
|
||
|
|
||
|
this.adaptPosterForAudioOnly()
|
||
|
|
||
|
if (options.isUserResolutionChange) {
|
||
|
this.player.trigger('user-resolution-change')
|
||
|
this.player.trigger('web-video-source-change')
|
||
|
|
||
|
this.tryToPlay()
|
||
|
.then(() => {
|
||
|
if (paused) this.player.pause()
|
||
|
|
||
|
this.player.autoplay(oldAutoplayValue)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.player.one('canplay', this.onPlayHandler)
|
||
|
}
|
||
|
|
||
|
getCurrentVideoFile () {
|
||
|
return this.currentVideoFile
|
||
|
}
|
||
|
|
||
|
private adaptPosterForAudioOnly () {
|
||
|
// Audio-only (resolutionId === 0) gets special treatment
|
||
|
if (this.currentVideoFile.resolution.id === 0) {
|
||
|
this.player.audioPosterMode(true)
|
||
|
} else {
|
||
|
this.player.audioPosterMode(false)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private tryToPlay () {
|
||
|
debugLogger('Try to play manually the video')
|
||
|
|
||
|
const playPromise = this.player.play()
|
||
|
if (playPromise === undefined) return
|
||
|
|
||
|
return playPromise
|
||
|
.catch((err: Error) => {
|
||
|
if (err.message.includes('The play() request was interrupted by a call to pause()')) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
logger.warn(err)
|
||
|
this.player.pause()
|
||
|
this.player.posterImage.show()
|
||
|
this.player.removeClass('vjs-has-autoplay')
|
||
|
this.player.removeClass('vjs-playing-audio-only-content')
|
||
|
})
|
||
|
.finally(() => {
|
||
|
this.player.removeClass('vjs-updating-resolution')
|
||
|
})
|
||
|
}
|
||
|
|
||
|
private pickAverageVideoFile () {
|
||
|
if (this.videoFiles.length === 1) return this.videoFiles[0]
|
||
|
|
||
|
const files = this.videoFiles.filter(f => f.resolution.id !== 0)
|
||
|
return files[Math.floor(files.length / 2)]
|
||
|
}
|
||
|
|
||
|
private buildQualities () {
|
||
|
const resolutions: PeerTubeResolution[] = this.videoFiles.map(videoFile => ({
|
||
|
id: videoFile.resolution.id,
|
||
|
label: this.buildQualityLabel(videoFile),
|
||
|
height: videoFile.resolution.id,
|
||
|
selected: videoFile.id === this.currentVideoFile.id,
|
||
|
selectCallback: () => this.updateVideoFile({ videoFile, isUserResolutionChange: true })
|
||
|
}))
|
||
|
|
||
|
this.player.peertubeResolutions().add(resolutions)
|
||
|
}
|
||
|
|
||
|
private buildQualityLabel (file: VideoFile) {
|
||
|
let label = file.resolution.label
|
||
|
|
||
|
if (file.fps && file.fps >= 50) {
|
||
|
label += file.fps
|
||
|
}
|
||
|
|
||
|
return label
|
||
|
}
|
||
|
|
||
|
private setupNetworkInfoInterval () {
|
||
|
this.networkInfoInterval = setInterval(() => {
|
||
|
return this.player.trigger('http-info', {
|
||
|
source: 'web-video',
|
||
|
http: {
|
||
|
downloaded: this.player.bufferedPercent() * this.currentVideoFile.size
|
||
|
}
|
||
|
} as PlayerNetworkInfo)
|
||
|
}, 1000)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
videojs.registerPlugin('webVideo', WebVideoPlugin)
|
||
|
export { WebVideoPlugin }
|