// Thanks https://github.com/streamroot/videojs-hlsjs-plugin
// We duplicated this plugin to choose the hls.js version we want, because streamroot only provide a bundled file
-import * as Hlsjs from 'hls.js'
-import videojs, { VideoJsPlayer } from 'video.js'
+import * as Hlsjs from 'hls.js/dist/hls.light.js'
+import videojs from 'video.js'
import { HlsjsConfigHandlerOptions, QualityLevelRepresentation, QualityLevels, VideoJSTechHLS } from '../peertube-videojs-typings'
type ErrorCounts = {
levels: Hlsjs.Level[]
}
+type CustomAudioTrack = Hlsjs.HlsAudioTrack & { name?: string, lang?: string }
+
const registerSourceHandler = function (vjs: typeof videojs) {
if (!Hlsjs.isSupported()) {
console.warn('Hls.js is not supported in this browser!')
(vjs as any).Html5Hlsjs = Html5Hlsjs
}
-function hlsjsConfigHandler (this: VideoJsPlayer, options: HlsjsConfigHandlerOptions) {
+function hlsjsConfigHandler (this: videojs.Player, options: HlsjsConfigHandlerOptions) {
const player = this
if (!options) return
private readonly videoElement: HTMLVideoElement
private readonly errorCounts: ErrorCounts = {}
- private readonly player: VideoJsPlayer
+ private readonly player: videojs.Player
private readonly tech: videojs.Tech
private readonly source: videojs.Tech.SourceObject
private readonly vjs: typeof videojs
- private hls: Hlsjs & { manualLevel?: number } // FIXME: typings
+ private hls: Hlsjs & { manualLevel?: number, audioTrack?: any, audioTracks?: CustomAudioTrack[] } // FIXME: typings
private hlsjsConfig: Partial<Hlsjs.Config & { cueHandler: any }> = null
private _duration: number = null
private dvrDuration: number = null
private edgeMargin: number = null
- private handlers: { [ id in 'play' | 'addtrack' | 'playing' | 'textTracksChange' | 'audioTracksChange' ]: EventListener } = {
+ private handlers: { [ id in 'play' | 'playing' | 'textTracksChange' | 'audioTracksChange' ]: EventListener } = {
play: null,
- addtrack: null,
playing: null,
textTracksChange: null,
audioTracksChange: null
this.videoElement.addEventListener('error', event => {
let errorTxt: string
- const mediaError = (event.currentTarget as HTMLVideoElement).error
+ const mediaError = ((event.currentTarget || event.target) as HTMLVideoElement).error
+
+ if (!mediaError) return
+ console.log(mediaError)
switch (mediaError.code) {
case mediaError.MEDIA_ERR_ABORTED:
errorTxt = 'You aborted the video playback'
// See comment for `initialize` method.
dispose () {
this.videoElement.removeEventListener('play', this.handlers.play)
- this.videoElement.textTracks.removeEventListener('addtrack', this.handlers.addtrack)
this.videoElement.removeEventListener('playing', this.handlers.playing)
this.player.textTracks().removeEventListener('change', this.handlers.textTracksChange)
this.uiTextTrackHandled = false
- this.player.audioTracks().removeEventListener('change', this.handlers.audioTracksChange)
-
this.hls.destroy()
}
if (this.errorCounts[ Hlsjs.ErrorTypes.MEDIA_ERROR ] > 2) {
console.info('bubbling media error up to VIDEOJS')
+ this.hls.destroy()
this.tech.error = () => error
this.tech.trigger('error')
return
}
}
+ private _handleNetworkError (error: any) {
+ if (this.errorCounts[ Hlsjs.ErrorTypes.NETWORK_ERROR] <= 5) {
+ console.info('trying to recover network error')
+
+ // Wait 1 second and retry
+ setTimeout(() => this.hls.startLoad(), 1000)
+
+ // Reset error count on success
+ this.hls.once(Hlsjs.Events.FRAG_LOADED, () => {
+ this.errorCounts[ Hlsjs.ErrorTypes.NETWORK_ERROR] = 0
+ })
+
+ return
+ }
+
+ console.info('bubbling network error up to VIDEOJS')
+ this.hls.destroy()
+ this.tech.error = () => error
+ this.tech.trigger('error')
+ }
+
private _onError (_event: any, data: Hlsjs.errorData) {
const error: { message: string, code?: number } = {
message: `HLS.js error: ${data.type} - fatal: ${data.fatal} - ${data.details}`
}
- console.error(error.message)
// increment/set error count
if (this.errorCounts[ data.type ]) this.errorCounts[ data.type ] += 1
else this.errorCounts[ data.type ] = 1
- // Implement simple error handling based on hls.js documentation
- // https://github.com/dailymotion/hls.js/blob/master/API.md#fifth-step-error-handling
- if (data.fatal) {
- switch (data.type) {
- case Hlsjs.ErrorTypes.NETWORK_ERROR:
- console.info('bubbling network error up to VIDEOJS')
- error.code = 2
- this.tech.error = () => error as any
- this.tech.trigger('error')
- break
+ if (!data.fatal) {
+ console.warn(error.message)
+ return
+ }
- case Hlsjs.ErrorTypes.MEDIA_ERROR:
- error.code = 3
- this._handleMediaError(error)
- break
+ console.error(error.message)
- default:
- // cannot recover
- this.hls.destroy()
- console.info('bubbling error up to VIDEOJS')
- this.tech.error = () => error as any
- this.tech.trigger('error')
- break
- }
+ if (data.type === Hlsjs.ErrorTypes.NETWORK_ERROR) {
+ error.code = 2
+ this._handleNetworkError(error)
+ } else if (data.type === Hlsjs.ErrorTypes.MEDIA_ERROR && data.details !== 'manifestIncompatibleCodecsError') {
+ error.code = 3
+ this._handleMediaError(error)
+ } else {
+ this.hls.destroy()
+ console.info('bubbling error up to VIDEOJS')
+ this.tech.error = () => error as any
+ this.tech.trigger('error')
}
}
}
private _onAudioTracks () {
- const hlsAudioTracks = this.hls.audioTracks as (AudioTrack & { name?: string, lang?: string })[] // FIXME typings
+ const hlsAudioTracks = this.hls.audioTracks
const playerAudioTracks = this.tech.audioTracks()
if (hlsAudioTracks.length > 1 && playerAudioTracks.length === 0) {
this.hls.attachMedia(this.videoElement)
- this.handlers.addtrack = this._updateTextTrackList.bind(this)
- this.videoElement.textTracks.addEventListener('addtrack', this.handlers.addtrack)
-
this.hls.loadSource(this.source.src)
}