aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/assets/player/p2p-media-loader/hls-plugin.ts
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/assets/player/p2p-media-loader/hls-plugin.ts')
-rw-r--r--client/src/assets/player/p2p-media-loader/hls-plugin.ts94
1 files changed, 48 insertions, 46 deletions
diff --git a/client/src/assets/player/p2p-media-loader/hls-plugin.ts b/client/src/assets/player/p2p-media-loader/hls-plugin.ts
index 78f0944ef..a1b07aea6 100644
--- a/client/src/assets/player/p2p-media-loader/hls-plugin.ts
+++ b/client/src/assets/player/p2p-media-loader/hls-plugin.ts
@@ -13,6 +13,8 @@ type Metadata = {
13 levels: Level[] 13 levels: Level[]
14} 14}
15 15
16type HookFn = (player: videojs.Player, hljs: Hlsjs) => void
17
16const registerSourceHandler = function (vjs: typeof videojs) { 18const registerSourceHandler = function (vjs: typeof videojs) {
17 if (!Hlsjs.isSupported()) { 19 if (!Hlsjs.isSupported()) {
18 console.warn('Hls.js is not supported in this browser!') 20 console.warn('Hls.js is not supported in this browser!')
@@ -82,7 +84,7 @@ const registerConfigPlugin = function (vjs: typeof videojs) {
82} 84}
83 85
84class Html5Hlsjs { 86class Html5Hlsjs {
85 private static readonly hooks: { [id: string]: Function[] } = {} 87 private static readonly hooks: { [id: string]: HookFn[] } = {}
86 88
87 private readonly videoElement: HTMLVideoElement 89 private readonly videoElement: HTMLVideoElement
88 private readonly errorCounts: ErrorCounts = {} 90 private readonly errorCounts: ErrorCounts = {}
@@ -131,7 +133,8 @@ class Html5Hlsjs {
131 errorTxt = 'You aborted the video playback' 133 errorTxt = 'You aborted the video playback'
132 break 134 break
133 case mediaError.MEDIA_ERR_DECODE: 135 case mediaError.MEDIA_ERR_DECODE:
134 errorTxt = 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support' 136 errorTxt = 'The video playback was aborted due to a corruption problem or because the video used features ' +
137 'your browser did not support'
135 this._handleMediaError(mediaError) 138 this._handleMediaError(mediaError)
136 break 139 break
137 case mediaError.MEDIA_ERR_NETWORK: 140 case mediaError.MEDIA_ERR_NETWORK:
@@ -182,58 +185,57 @@ class Html5Hlsjs {
182 this.hls.destroy() 185 this.hls.destroy()
183 } 186 }
184 187
185 static addHook (type: string, callback: Function) { 188 static addHook (type: string, callback: HookFn) {
186 Html5Hlsjs.hooks[ type ] = this.hooks[ type ] || [] 189 Html5Hlsjs.hooks[type] = this.hooks[type] || []
187 Html5Hlsjs.hooks[ type ].push(callback) 190 Html5Hlsjs.hooks[type].push(callback)
188 } 191 }
189 192
190 static removeHook (type: string, callback: Function) { 193 static removeHook (type: string, callback: HookFn) {
191 if (Html5Hlsjs.hooks[ type ] === undefined) return false 194 if (Html5Hlsjs.hooks[type] === undefined) return false
192 195
193 const index = Html5Hlsjs.hooks[ type ].indexOf(callback) 196 const index = Html5Hlsjs.hooks[type].indexOf(callback)
194 if (index === -1) return false 197 if (index === -1) return false
195 198
196 Html5Hlsjs.hooks[ type ].splice(index, 1) 199 Html5Hlsjs.hooks[type].splice(index, 1)
197 200
198 return true 201 return true
199 } 202 }
200 203
201 private _executeHooksFor (type: string) { 204 private _executeHooksFor (type: string) {
202 if (Html5Hlsjs.hooks[ type ] === undefined) { 205 if (Html5Hlsjs.hooks[type] === undefined) {
203 return 206 return
204 } 207 }
205 208
206 // ES3 and IE < 9 209 // ES3 and IE < 9
207 for (let i = 0; i < Html5Hlsjs.hooks[ type ].length; i++) { 210 for (let i = 0; i < Html5Hlsjs.hooks[type].length; i++) {
208 Html5Hlsjs.hooks[ type ][ i ](this.player, this.hls) 211 Html5Hlsjs.hooks[type][i](this.player, this.hls)
209 } 212 }
210 } 213 }
211 214
212 private _handleMediaError (error: any) { 215 private _handleMediaError (error: any) {
213 if (this.errorCounts[ Hlsjs.ErrorTypes.MEDIA_ERROR ] === 1) { 216 if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] === 1) {
214 console.info('trying to recover media error') 217 console.info('trying to recover media error')
215 this.hls.recoverMediaError() 218 this.hls.recoverMediaError()
216 return 219 return
217 } 220 }
218 221
219 if (this.errorCounts[ Hlsjs.ErrorTypes.MEDIA_ERROR ] === 2) { 222 if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] === 2) {
220 console.info('2nd try to recover media error (by swapping audio codec') 223 console.info('2nd try to recover media error (by swapping audio codec')
221 this.hls.swapAudioCodec() 224 this.hls.swapAudioCodec()
222 this.hls.recoverMediaError() 225 this.hls.recoverMediaError()
223 return 226 return
224 } 227 }
225 228
226 if (this.errorCounts[ Hlsjs.ErrorTypes.MEDIA_ERROR ] > 2) { 229 if (this.errorCounts[Hlsjs.ErrorTypes.MEDIA_ERROR] > 2) {
227 console.info('bubbling media error up to VIDEOJS') 230 console.info('bubbling media error up to VIDEOJS')
228 this.hls.destroy() 231 this.hls.destroy()
229 this.tech.error = () => error 232 this.tech.error = () => error
230 this.tech.trigger('error') 233 this.tech.trigger('error')
231 return
232 } 234 }
233 } 235 }
234 236
235 private _handleNetworkError (error: any) { 237 private _handleNetworkError (error: any) {
236 if (this.errorCounts[ Hlsjs.ErrorTypes.NETWORK_ERROR] <= 5) { 238 if (this.errorCounts[Hlsjs.ErrorTypes.NETWORK_ERROR] <= 5) {
237 console.info('trying to recover network error') 239 console.info('trying to recover network error')
238 240
239 // Wait 1 second and retry 241 // Wait 1 second and retry
@@ -241,7 +243,7 @@ class Html5Hlsjs {
241 243
242 // Reset error count on success 244 // Reset error count on success
243 this.hls.once(Hlsjs.Events.FRAG_LOADED, () => { 245 this.hls.once(Hlsjs.Events.FRAG_LOADED, () => {
244 this.errorCounts[ Hlsjs.ErrorTypes.NETWORK_ERROR] = 0 246 this.errorCounts[Hlsjs.ErrorTypes.NETWORK_ERROR] = 0
245 }) 247 })
246 248
247 return 249 return
@@ -259,8 +261,8 @@ class Html5Hlsjs {
259 } 261 }
260 262
261 // increment/set error count 263 // increment/set error count
262 if (this.errorCounts[ data.type ]) this.errorCounts[ data.type ] += 1 264 if (this.errorCounts[data.type]) this.errorCounts[data.type] += 1
263 else this.errorCounts[ data.type ] = 1 265 else this.errorCounts[data.type] = 1
264 266
265 if (data.fatal) console.warn(error.message) 267 if (data.fatal) console.warn(error.message)
266 else console.error(error.message, data) 268 else console.error(error.message, data)
@@ -300,7 +302,7 @@ class Html5Hlsjs {
300 let isAuto = true 302 let isAuto = true
301 303
302 for (let i = 0; i < qualityLevels.length; i++) { 304 for (let i = 0; i < qualityLevels.length; i++) {
303 if (!qualityLevels[ i ]._enabled) { 305 if (!qualityLevels[i]._enabled) {
304 isAuto = false 306 isAuto = false
305 break 307 break
306 } 308 }
@@ -316,7 +318,7 @@ class Html5Hlsjs {
316 let selectedTrack: number 318 let selectedTrack: number
317 319
318 for (selectedTrack = qualityLevels.length - 1; selectedTrack >= 0; selectedTrack--) { 320 for (selectedTrack = qualityLevels.length - 1; selectedTrack >= 0; selectedTrack--) {
319 if (qualityLevels[ selectedTrack ]._enabled) { 321 if (qualityLevels[selectedTrack]._enabled) {
320 break 322 break
321 } 323 }
322 } 324 }
@@ -327,11 +329,11 @@ class Html5Hlsjs {
327 private _handleQualityLevels () { 329 private _handleQualityLevels () {
328 if (!this.metadata) return 330 if (!this.metadata) return
329 331
330 const qualityLevels = this.player.qualityLevels && this.player.qualityLevels() 332 const qualityLevels = this.player.qualityLevels?.()
331 if (!qualityLevels) return 333 if (!qualityLevels) return
332 334
333 for (let i = 0; i < this.metadata.levels.length; i++) { 335 for (let i = 0; i < this.metadata.levels.length; i++) {
334 const details = this.metadata.levels[ i ] 336 const details = this.metadata.levels[i]
335 const representation: QualityLevelRepresentation = { 337 const representation: QualityLevelRepresentation = {
336 id: i, 338 id: i,
337 width: details.width, 339 width: details.width,
@@ -345,11 +347,11 @@ class Html5Hlsjs {
345 representation.enabled = function (this: QualityLevels, level: number, toggle?: boolean) { 347 representation.enabled = function (this: QualityLevels, level: number, toggle?: boolean) {
346 // Brightcove switcher works TextTracks-style (enable tracks that it wants to ABR on) 348 // Brightcove switcher works TextTracks-style (enable tracks that it wants to ABR on)
347 if (typeof toggle === 'boolean') { 349 if (typeof toggle === 'boolean') {
348 this[ level ]._enabled = toggle 350 this[level]._enabled = toggle
349 self._relayQualityChange(this) 351 self._relayQualityChange(this)
350 } 352 }
351 353
352 return this[ level ]._enabled 354 return this[level]._enabled
353 } 355 }
354 356
355 qualityLevels.addQualityLevel(representation) 357 qualityLevels.addQualityLevel(representation)
@@ -395,7 +397,7 @@ class Html5Hlsjs {
395 const playerAudioTracks = this.tech.audioTracks() 397 const playerAudioTracks = this.tech.audioTracks()
396 for (let j = 0; j < playerAudioTracks.length; j++) { 398 for (let j = 0; j < playerAudioTracks.length; j++) {
397 // FIXME: typings 399 // FIXME: typings
398 if ((playerAudioTracks[ j ] as any).enabled) { 400 if ((playerAudioTracks[j] as any).enabled) {
399 this.hls.audioTrack = j 401 this.hls.audioTrack = j
400 break 402 break
401 } 403 }
@@ -412,8 +414,8 @@ class Html5Hlsjs {
412 playerAudioTracks.addTrack(new this.vjs.AudioTrack({ 414 playerAudioTracks.addTrack(new this.vjs.AudioTrack({
413 id: i.toString(), 415 id: i.toString(),
414 kind: 'alternative', 416 kind: 'alternative',
415 label: hlsAudioTracks[ i ].name || hlsAudioTracks[ i ].lang, 417 label: hlsAudioTracks[i].name || hlsAudioTracks[i].lang,
416 language: hlsAudioTracks[ i ].lang, 418 language: hlsAudioTracks[i].lang,
417 enabled: i === this.hls.audioTrack 419 enabled: i === this.hls.audioTrack
418 })) 420 }))
419 } 421 }
@@ -430,8 +432,8 @@ class Html5Hlsjs {
430 } 432 }
431 433
432 private _isSameTextTrack (track1: TextTrack, track2: TextTrack) { 434 private _isSameTextTrack (track1: TextTrack, track2: TextTrack) {
433 return this._getTextTrackLabel(track1) === this._getTextTrackLabel(track2) 435 return this._getTextTrackLabel(track1) === this._getTextTrackLabel(track2) &&
434 && track1.kind === track2.kind 436 track1.kind === track2.kind
435 } 437 }
436 438
437 private _updateSelectedTextTrack () { 439 private _updateSelectedTextTrack () {
@@ -439,16 +441,16 @@ class Html5Hlsjs {
439 let activeTrack: TextTrack = null 441 let activeTrack: TextTrack = null
440 442
441 for (let j = 0; j < playerTextTracks.length; j++) { 443 for (let j = 0; j < playerTextTracks.length; j++) {
442 if (playerTextTracks[ j ].mode === 'showing') { 444 if (playerTextTracks[j].mode === 'showing') {
443 activeTrack = playerTextTracks[ j ] 445 activeTrack = playerTextTracks[j]
444 break 446 break
445 } 447 }
446 } 448 }
447 449
448 const hlsjsTracks = this.videoElement.textTracks 450 const hlsjsTracks = this.videoElement.textTracks
449 for (let k = 0; k < hlsjsTracks.length; k++) { 451 for (let k = 0; k < hlsjsTracks.length; k++) {
450 if (hlsjsTracks[ k ].kind === 'subtitles' || hlsjsTracks[ k ].kind === 'captions') { 452 if (hlsjsTracks[k].kind === 'subtitles' || hlsjsTracks[k].kind === 'captions') {
451 hlsjsTracks[ k ].mode = activeTrack && this._isSameTextTrack(hlsjsTracks[ k ], activeTrack) 453 hlsjsTracks[k].mode = activeTrack && this._isSameTextTrack(hlsjsTracks[k], activeTrack)
452 ? 'showing' 454 ? 'showing'
453 : 'disabled' 455 : 'disabled'
454 } 456 }
@@ -460,11 +462,11 @@ class Html5Hlsjs {
460 this.videoElement.removeEventListener('play', this.handlers.play) 462 this.videoElement.removeEventListener('play', this.handlers.play)
461 } 463 }
462 464
463 private _oneLevelObjClone (obj: object) { 465 private _oneLevelObjClone (obj: { [ id: string ]: any }) {
464 const result = {} 466 const result = {}
465 const objKeys = Object.keys(obj) 467 const objKeys = Object.keys(obj)
466 for (let i = 0; i < objKeys.length; i++) { 468 for (let i = 0; i < objKeys.length; i++) {
467 result[ objKeys[ i ] ] = obj[ objKeys[ i ] ] 469 result[objKeys[i]] = obj[objKeys[i]]
468 } 470 }
469 471
470 return result 472 return result
@@ -475,8 +477,8 @@ class Html5Hlsjs {
475 477
476 // Filter out tracks that is displayable (captions or subtitles) 478 // Filter out tracks that is displayable (captions or subtitles)
477 for (let idx = 0; idx < textTracks.length; idx++) { 479 for (let idx = 0; idx < textTracks.length; idx++) {
478 if (textTracks[ idx ].kind === 'subtitles' || textTracks[ idx ].kind === 'captions') { 480 if (textTracks[idx].kind === 'subtitles' || textTracks[idx].kind === 'captions') {
479 displayableTracks.push(textTracks[ idx ]) 481 displayableTracks.push(textTracks[idx])
480 } 482 }
481 } 483 }
482 484
@@ -493,14 +495,14 @@ class Html5Hlsjs {
493 let isAdded = false 495 let isAdded = false
494 496
495 for (let jdx = 0; jdx < playerTextTracks.length; jdx++) { 497 for (let jdx = 0; jdx < playerTextTracks.length; jdx++) {
496 if (this._isSameTextTrack(displayableTracks[ idx ], playerTextTracks[ jdx ])) { 498 if (this._isSameTextTrack(displayableTracks[idx], playerTextTracks[jdx])) {
497 isAdded = true 499 isAdded = true
498 break 500 break
499 } 501 }
500 } 502 }
501 503
502 if (!isAdded) { 504 if (!isAdded) {
503 const hlsjsTextTrack = displayableTracks[ idx ] 505 const hlsjsTextTrack = displayableTracks[idx]
504 this.player.addRemoteTextTrack({ 506 this.player.addRemoteTextTrack({
505 kind: hlsjsTextTrack.kind as videojs.TextTrack.Kind, 507 kind: hlsjsTextTrack.kind as videojs.TextTrack.Kind,
506 label: this._getTextTrackLabel(hlsjsTextTrack), 508 label: this._getTextTrackLabel(hlsjsTextTrack),
@@ -536,12 +538,12 @@ class Html5Hlsjs {
536 const VTTCue = (window as any).VTTCue || (window as any).TextTrackCue 538 const VTTCue = (window as any).VTTCue || (window as any).TextTrackCue
537 539
538 for (let r = 0; r < captionScreen.rows.length; r++) { 540 for (let r = 0; r < captionScreen.rows.length; r++) {
539 row = captionScreen.rows[ r ] 541 row = captionScreen.rows[r]
540 text = '' 542 text = ''
541 543
542 if (!row.isEmpty()) { 544 if (!row.isEmpty()) {
543 for (let c = 0; c < row.chars.length; c++) { 545 for (let c = 0; c < row.chars.length; c++) {
544 text += row.chars[ c ].ucharj 546 text += row.chars[c].ucharj
545 } 547 }
546 548
547 cue = new VTTCue(startTime, endTime, text.trim()) 549 cue = new VTTCue(startTime, endTime, text.trim())
@@ -552,7 +554,7 @@ class Html5Hlsjs {
552 const configKeys = Object.keys(captionConfig) 554 const configKeys = Object.keys(captionConfig)
553 555
554 for (let k = 0; k < configKeys.length; k++) { 556 for (let k = 0; k < configKeys.length; k++) {
555 cue[ configKeys[ k ] ] = captionConfig[ configKeys[ k ] ] 557 cue[configKeys[k]] = captionConfig[configKeys[k]]
556 } 558 }
557 } 559 }
558 track.addCue(cue) 560 track.addCue(cue)
@@ -567,7 +569,7 @@ class Html5Hlsjs {
567 const techOptions = this.tech.options_ as HlsjsConfigHandlerOptions 569 const techOptions = this.tech.options_ as HlsjsConfigHandlerOptions
568 const srOptions_ = this.player.srOptions_ 570 const srOptions_ = this.player.srOptions_
569 571
570 const hlsjsConfigRef = srOptions_ && srOptions_.hlsjsConfig || techOptions.hlsjsConfig 572 const hlsjsConfigRef = srOptions_?.hlsjsConfig || techOptions.hlsjsConfig
571 // Hls.js will write to the reference thus change the object for later streams 573 // Hls.js will write to the reference thus change the object for later streams
572 this.hlsjsConfig = hlsjsConfigRef ? this._oneLevelObjClone(hlsjsConfigRef) : {} 574 this.hlsjsConfig = hlsjsConfigRef ? this._oneLevelObjClone(hlsjsConfigRef) : {}
573 575
@@ -575,7 +577,7 @@ class Html5Hlsjs {
575 this.hlsjsConfig.autoStartLoad = false 577 this.hlsjsConfig.autoStartLoad = false
576 } 578 }
577 579
578 const captionConfig = srOptions_ && srOptions_.captionConfig || techOptions.captionConfig 580 const captionConfig = srOptions_?.captionConfig || techOptions.captionConfig
579 if (captionConfig) { 581 if (captionConfig) {
580 this.hlsjsConfig.cueHandler = this._createCueHandler(captionConfig) 582 this.hlsjsConfig.cueHandler = this._createCueHandler(captionConfig)
581 } 583 }