diff options
Diffstat (limited to 'client/src/assets/player/p2p-media-loader')
-rw-r--r-- | client/src/assets/player/p2p-media-loader/hls-plugin.ts | 94 | ||||
-rw-r--r-- | client/src/assets/player/p2p-media-loader/segment-validator.ts | 5 |
2 files changed, 52 insertions, 47 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 | ||
16 | type HookFn = (player: videojs.Player, hljs: Hlsjs) => void | ||
17 | |||
16 | const registerSourceHandler = function (vjs: typeof videojs) { | 18 | const 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 | ||
84 | class Html5Hlsjs { | 86 | class 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 | } |
diff --git a/client/src/assets/player/p2p-media-loader/segment-validator.ts b/client/src/assets/player/p2p-media-loader/segment-validator.ts index d0a4c4a3f..f7f83a8a4 100644 --- a/client/src/assets/player/p2p-media-loader/segment-validator.ts +++ b/client/src/assets/player/p2p-media-loader/segment-validator.ts | |||
@@ -86,6 +86,7 @@ async function sha256Hex (data?: ArrayBuffer) { | |||
86 | 86 | ||
87 | // Fallback for non HTTPS context | 87 | // Fallback for non HTTPS context |
88 | const shaModule = (await import('sha.js') as any).default | 88 | const shaModule = (await import('sha.js') as any).default |
89 | // eslint-disable-next-line new-cap | ||
89 | return new shaModule.sha256().update(Buffer.from(data)).digest('hex') | 90 | return new shaModule.sha256().update(Buffer.from(data)).digest('hex') |
90 | } | 91 | } |
91 | 92 | ||
@@ -97,7 +98,9 @@ function bufferToHex (buffer?: ArrayBuffer) { | |||
97 | const h = '0123456789abcdef' | 98 | const h = '0123456789abcdef' |
98 | const o = new Uint8Array(buffer) | 99 | const o = new Uint8Array(buffer) |
99 | 100 | ||
100 | o.forEach((v: any) => s += h[ v >> 4 ] + h[ v & 15 ]) | 101 | o.forEach((v: any) => { |
102 | s += h[v >> 4] + h[v & 15] | ||
103 | }) | ||
101 | 104 | ||
102 | return s | 105 | return s |
103 | } | 106 | } |