@gcorevideo/player 2.10.0 → 2.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE +13 -0
  2. package/coverage/clover.xml +6 -0
  3. package/coverage/coverage-final.json +1 -0
  4. package/coverage/lcov-report/base.css +224 -0
  5. package/coverage/lcov-report/block-navigation.js +87 -0
  6. package/coverage/lcov-report/favicon.png +0 -0
  7. package/coverage/lcov-report/index.html +101 -0
  8. package/coverage/lcov-report/prettify.css +1 -0
  9. package/coverage/lcov-report/prettify.js +2 -0
  10. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  11. package/coverage/lcov-report/sorter.js +196 -0
  12. package/coverage/lcov.info +0 -0
  13. package/dist/index.js +3115 -1209
  14. package/lib/Player.d.ts +3 -2
  15. package/lib/Player.d.ts.map +1 -1
  16. package/lib/Player.js +32 -24
  17. package/lib/index.d.ts +5 -5
  18. package/lib/index.d.ts.map +1 -1
  19. package/lib/index.js +5 -5
  20. package/lib/internal.types.d.ts +1 -10
  21. package/lib/internal.types.d.ts.map +1 -1
  22. package/lib/playback/index.d.ts +4 -0
  23. package/lib/playback/index.d.ts.map +1 -0
  24. package/lib/playback/index.js +13 -0
  25. package/lib/playback.types.d.ts +19 -0
  26. package/lib/playback.types.d.ts.map +1 -1
  27. package/lib/playback.types.js +9 -1
  28. package/lib/plugins/dash-playback/DashPlayback.d.ts +1 -1
  29. package/lib/plugins/dash-playback/DashPlayback.d.ts.map +1 -1
  30. package/lib/plugins/dash-playback/DashPlayback.js +39 -100
  31. package/lib/plugins/dash-playback/types.d.ts +6 -0
  32. package/lib/plugins/dash-playback/types.d.ts.map +1 -0
  33. package/lib/plugins/dash-playback/types.js +1 -0
  34. package/lib/plugins/hls-playback/HlsPlayback.d.ts +6 -7
  35. package/lib/plugins/hls-playback/HlsPlayback.d.ts.map +1 -1
  36. package/lib/plugins/hls-playback/HlsPlayback.js +131 -80
  37. package/lib/types.d.ts +3 -3
  38. package/lib/types.d.ts.map +1 -1
  39. package/lib/utils/mediaSources.d.ts +14 -6
  40. package/lib/utils/mediaSources.d.ts.map +1 -1
  41. package/lib/utils/mediaSources.js +56 -53
  42. package/lib/utils/testUtils.d.ts +3 -0
  43. package/lib/utils/testUtils.d.ts.map +1 -0
  44. package/lib/utils/testUtils.js +12 -0
  45. package/package.json +6 -4
  46. package/src/Player.ts +40 -31
  47. package/src/__tests__/Player.test.ts +357 -0
  48. package/src/index.ts +5 -5
  49. package/src/internal.types.ts +1 -12
  50. package/src/playback/index.ts +17 -0
  51. package/src/playback.types.ts +29 -8
  52. package/src/plugins/dash-playback/DashPlayback.ts +44 -120
  53. package/src/plugins/hls-playback/HlsPlayback.ts +544 -390
  54. package/src/types.ts +5 -3
  55. package/src/typings/@clappr/core/error_mixin.d.ts +0 -2
  56. package/src/typings/@clappr/core/index.d.ts +5 -0
  57. package/src/typings/@clappr/index.d.ts +1 -0
  58. package/src/utils/__tests__/mediaSources.test.ts +230 -0
  59. package/src/utils/mediaSources.ts +78 -64
  60. package/src/utils/testUtils.ts +15 -0
  61. package/tsconfig.json +0 -9
  62. package/tsconfig.tsbuildinfo +1 -1
  63. package/vitest.config.ts +8 -0
  64. package/licenses.json +0 -782
  65. package/src/utils/queryParams.ts +0 -5
@@ -5,6 +5,7 @@ import type {
5
5
  } from "@clappr/core";
6
6
 
7
7
  import { PlaybackType, PlayerDebugTag, PlayerMediaSource } from "./types";
8
+ import { PlaybackError } from "./playback.types";
8
9
 
9
10
  type ExternalTrack = {
10
11
  kind?: "subtitles" | "captions";
@@ -56,18 +57,6 @@ export interface CorePlaybackConfig {
56
57
  shakaConfiguration?: ShakaConfig;
57
58
  }
58
59
 
59
- type ErrorLevel = "FATAL" | "WARN" | "INFO";
60
-
61
- /**
62
- * @internal
63
- */
64
- export type PlaybackError = {
65
- code?: number | string;
66
- description: string;
67
- raw?: MediaError;
68
- level?: ErrorLevel;
69
- }
70
-
71
60
  /**
72
61
  * @internal
73
62
  */
@@ -0,0 +1,17 @@
1
+ import { Loader } from '@clappr/core'
2
+
3
+ import DashPlayback from '../plugins/dash-playback/DashPlayback.js'
4
+ import HlsPlayback from '../plugins/hls-playback/HlsPlayback.js'
5
+
6
+ export function registerPlaybacks() {
7
+ Loader.registerPlayback(DashPlayback)
8
+ Loader.registerPlayback(HlsPlayback)
9
+ }
10
+
11
+ export function canPlayDash(source: string, mimeType?: string) {
12
+ return DashPlayback.canPlay(source, mimeType)
13
+ }
14
+
15
+ export function canPlayHls(source: string, mimeType?: string) {
16
+ return HlsPlayback.canPlay(source, mimeType)
17
+ }
@@ -1,39 +1,60 @@
1
-
2
1
  /**
2
+ * Playback time in seconds since the beginning of the stream (or a segment for the live streams)
3
3
  * For the plugin development
4
4
  * @internal
5
5
  */
6
- export type TimeValue = number;
6
+ export type TimeValue = number
7
7
 
8
8
  /**
9
9
  * For the plugin development
10
10
  * @internal
11
11
  */
12
12
  export type TimePosition = {
13
- current: TimeValue;
14
- total: TimeValue;
13
+ current: TimeValue
14
+ total: TimeValue
15
15
  }
16
16
 
17
17
  /**
18
18
  * For the plugin development
19
19
  * @internal
20
20
  */
21
- export type TimeProgress = TimePosition & { start: number; };
21
+ export type TimeProgress = TimePosition & { start: number }
22
22
 
23
23
  /**
24
24
  * For the plugin development
25
25
  * @internal
26
26
  */
27
27
  export type TimeUpdate = TimePosition & {
28
- firstFragDateTime: number;
29
- };
28
+ firstFragDateTime: number
29
+ }
30
30
 
31
31
  /**
32
32
  * @beta
33
33
  */
34
34
  export type QualityLevel = {
35
- level: number // index
35
+ level: number // 0-based index
36
36
  width: number
37
37
  height: number
38
38
  bitrate: number
39
39
  }
40
+
41
+ /**
42
+ * @beta
43
+ */
44
+ export enum PlaybackErrorCode {
45
+ Generic = 0,
46
+ MediaSourceUnavailable = 1,
47
+ QualityLevelUnavailable = 2,
48
+ }
49
+
50
+ export type ErrorLevel = 'FATAL' | 'WARN' | 'INFO'
51
+
52
+ /**
53
+ * @beta
54
+ */
55
+ export interface PlaybackError {
56
+ code: PlaybackErrorCode
57
+ description: string
58
+ level: ErrorLevel
59
+ message: string
60
+ }
@@ -2,18 +2,19 @@
2
2
  // Use of this source code is governed by a BSD-style
3
3
  // license that can be found in the LICENSE file.
4
4
 
5
- import { Events, HTML5Video, Log, Playback, Utils } from '@clappr/core'
5
+ import { Events, HTML5Video, Log, Playback, PlayerError, Utils } from '@clappr/core'
6
+ import { trace } from '@gcorevideo/utils'
6
7
  import assert from 'assert'
7
8
  import DASHJS, {
8
9
  ErrorEvent as DashErrorEvent,
10
+ MediaPlayerErrorEvent,
9
11
  PlaybackErrorEvent as DashPlaybackErrorEvent,
10
12
  type BitrateInfo as DashBitrateInfo,
11
13
  MetricEvent as DashMetricEvent,
12
14
  IManifestInfo,
13
15
  } from 'dashjs'
14
- import { trace } from '@gcorevideo/utils'
15
16
 
16
- import { QualityLevel, TimePosition, TimeValue } from '../../playback.types.js'
17
+ import { PlaybackError, PlaybackErrorCode, QualityLevel, TimePosition, TimeUpdate, TimeValue } from '../../playback.types.js'
17
18
 
18
19
  const AUTO = -1
19
20
 
@@ -88,9 +89,6 @@ export default class DashPlayback extends HTML5Video {
88
89
 
89
90
  manifestInfo: IManifestInfo | null = null
90
91
 
91
- // #EXT-X-TARGETDURATION
92
- _segmentTargetDuration: TimeValue | null = null
93
-
94
92
  _timeUpdateTimer: ReturnType<typeof setInterval> | null = null
95
93
 
96
94
  get name() {
@@ -118,6 +116,12 @@ export default class DashPlayback extends HTML5Video {
118
116
 
119
117
  this.trigger(Events.PLAYBACK_LEVEL_SWITCH_START)
120
118
 
119
+ assert.ok(
120
+ this._dash,
121
+ 'An instance of dashjs MediaPlayer is required to switch levels',
122
+ )
123
+ const dash = this._dash
124
+
121
125
  // TODO use $.extend
122
126
  const settings = this.options.dash ? structuredClone(this.options.dash) : {}
123
127
  settings.streaming = settings.streaming || {}
@@ -126,11 +130,6 @@ export default class DashPlayback extends HTML5Video {
126
130
  settings.streaming.abr.autoSwitchBitrate || {}
127
131
  settings.streaming.abr.autoSwitchBitrate.video = id === -1
128
132
 
129
- assert.ok(
130
- this._dash,
131
- 'An instance of dashjs MediaPlayer is required to switch levels',
132
- )
133
- const dash = this._dash
134
133
  dash.updateSettings(settings)
135
134
  if (id !== -1) {
136
135
  this._dash.setQualityFor('video', id)
@@ -334,9 +333,6 @@ export default class DashPlayback extends HTML5Video {
334
333
  }
335
334
 
336
335
  getCurrentTime(): TimeValue {
337
- // e.g. can be < 0 if user pauses near the start
338
- // eventually they will then be kicked to the end by hlsjs if they run out of buffer
339
- // before the official start time
340
336
  return this._dash ? this._dash.time() : 0
341
337
  }
342
338
 
@@ -410,107 +406,39 @@ export default class DashPlayback extends HTML5Video {
410
406
 
411
407
  private _onDASHJSSError = (event: DashErrorEvent) => {
412
408
  trace(`${T} _onDASHJSSError`, { event })
413
- // TODO
414
- // only report/handle errors if they are fatal
415
409
  this._stopTimeUpdateTimer()
416
- if (event.error === 'capability' && event.event === 'mediasource') {
417
- // No support for MSE
418
- const formattedError = this.createError(event.error)
419
-
420
- this.trigger(Events.PLAYBACK_ERROR, formattedError)
421
- Log.error(
422
- 'The media cannot be played because it requires a feature ' +
423
- 'that your browser does not support.',
424
- )
425
- } else if (
426
- event.error === 'manifestError' &&
427
- // Manifest type not supported
428
- (event.event.id === 'createParser' ||
429
- // Codec(s) not supported
430
- event.event.id === 'codec' ||
431
- // No streams available to stream
432
- event.event.id === 'nostreams' ||
433
- // Error creating Stream object
434
- event.event.id === 'nostreamscomposed' ||
435
- // syntax error parsing the manifest
436
- event.event.id === 'parse' ||
437
- // a stream has multiplexed audio+video
438
- event.event.id === 'multiplexedrep')
439
- ) {
440
- // These errors have useful error messages, so we forward it on
441
- const formattedError = this.createError(event.error)
442
410
 
443
- this.trigger(Events.PLAYBACK_ERROR, formattedError)
444
- if (event.error) {
445
- Log.error(event.event.message)
446
- }
447
- } else if (event.error === 'mediasource') {
448
- // This error happens when dash.js fails to allocate a SourceBuffer
449
- // OR the underlying video element throws a `MediaError`.
450
- // If it's a buffer allocation fail, the message states which buffer
451
- // (audio/video/text) failed allocation.
452
- // If it's a `MediaError`, dash.js inspects the error object for
453
- // additional information to append to the error type.
454
- const formattedError = this.createError(event.error)
455
-
456
- this.trigger(Events.PLAYBACK_ERROR, formattedError)
457
- Log.error(event.event)
458
- } else if (
459
- event.error === 'capability' &&
460
- event.event === 'encryptedmedia'
461
- ) {
462
- // Browser doesn't support EME
463
-
464
- const formattedError = this.createError(event.error)
465
-
466
- this.trigger(Events.PLAYBACK_ERROR, formattedError)
467
- Log.error(
468
- 'The media cannot be played because it requires encryption ' +
469
- 'that your browser does not support.',
470
- )
471
- } else if (event.error === 'key_session') {
472
- // This block handles pretty much all errors thrown by the
473
- // encryption subsystem
474
- const formattedError = this.createError(event.error)
475
-
476
- this.trigger(Events.PLAYBACK_ERROR, formattedError)
477
- Log.error(event.event)
478
- } else if (event.error === 'download') {
479
- const formattedError = this.createError(event.error)
480
-
481
- this.trigger(Events.PLAYBACK_ERROR, formattedError)
482
- Log.error(
483
- 'The media playback was aborted because too many consecutive ' +
484
- 'download errors occurred.',
485
- )
486
- // } else if (event.error === 'mssError') {
487
- // const formattedError = this.createError(event.error);
488
-
489
- // this.trigger(Events.PLAYBACK_ERROR, formattedError);
490
- // if (event.error) {
491
- // Log.error(event.error.message);
492
- // }
493
- } else {
494
- // ignore the error
495
- if (typeof event.error === 'object') {
496
- const formattedError = this.createError(event.error)
497
-
498
- this.trigger(Events.PLAYBACK_ERROR, formattedError)
499
- Log.error(event.error.message)
500
- } else {
501
- Log.error(event.error)
502
- }
503
- return
411
+ const e = (event as MediaPlayerErrorEvent).error
412
+ switch (e.code) {
413
+ case DASHJS.MediaPlayer.errors.MANIFEST_LOADER_PARSING_FAILURE_ERROR_CODE:
414
+ case DASHJS.MediaPlayer.errors.MANIFEST_LOADER_LOADING_FAILURE_ERROR_CODE:
415
+ case DASHJS.MediaPlayer.errors.DOWNLOAD_ERROR_ID_MANIFEST_CODE:
416
+ case DASHJS.MediaPlayer.errors.DOWNLOAD_ERROR_ID_CONTENT_CODE:
417
+ this.triggerError({
418
+ code: PlaybackErrorCode.MediaSourceUnavailable,
419
+ message: e.message,
420
+ description: e.message,
421
+ level: PlayerError.Levels.FATAL,
422
+ })
423
+ break;
424
+ // TODO more cases
425
+ default:
426
+ this.triggerError({
427
+ code: PlaybackErrorCode.Generic,
428
+ message: e.message,
429
+ description: e.message,
430
+ level: PlayerError.Levels.FATAL,
431
+ })
504
432
  }
433
+ }
505
434
 
435
+ private triggerError(error: PlaybackError) {
436
+ trace(`${T} triggerError`, { error })
437
+ this.trigger(Events.PLAYBACK_ERROR, error)
506
438
  // only reset the dash player in 10ms async, so that the rest of the
507
439
  // calling function finishes
508
440
  setTimeout(() => {
509
- assert.ok(
510
- this._dash,
511
- 'An instance of dashjs MediaPlayer is required to reset',
512
- )
513
- this._dash.reset()
441
+ this.stop()
514
442
  }, 10)
515
443
  }
516
444
 
@@ -518,7 +446,7 @@ export default class DashPlayback extends HTML5Video {
518
446
  if (this.startChangeQuality) {
519
447
  return
520
448
  }
521
- const update = {
449
+ const update: TimeUpdate = {
522
450
  current: this.getCurrentTime(),
523
451
  total: this.getDuration(),
524
452
  firstFragDateTime: this.getProgramDateTime(),
@@ -650,6 +578,9 @@ export default class DashPlayback extends HTML5Video {
650
578
  }
651
579
 
652
580
  private onLevelSwitch(currentLevel: QualityLevel) {
581
+ // TODO check the two below
582
+ this.trigger(Events.PLAYBACK_LEVEL_SWITCH, currentLevel)
583
+ this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)
653
584
  const isHD = (currentLevel.height >= 720 || (currentLevel.bitrate / 1000) >= 2000);
654
585
  this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, isHD);
655
586
  this.trigger(Events.PLAYBACK_BITRATE, currentLevel)
@@ -670,21 +601,14 @@ DashPlayback.canPlay = function (resource, mimeType) {
670
601
  (resourceParts.length > 1 && resourceParts[1].toLowerCase() === 'mpd') ||
671
602
  mimeType === 'application/dash+xml' ||
672
603
  mimeType === 'video/mp4'
604
+ if (!isDash) {
605
+ return false
606
+ }
673
607
  const ms = window.MediaSource
674
608
  const mms =
675
609
  'ManagedMediaSource' in window ? window.ManagedMediaSource : undefined
676
610
  const wms =
677
611
  'WebKitMediaSource' in window ? window.WebKitMediaSource : undefined
678
612
  const ctor = ms || mms || wms
679
-
680
- const hasSupport = typeof ctor === 'function'
681
- trace(`${T} canPlay`, {
682
- hasSupport,
683
- isDash,
684
- resource,
685
- ms: typeof ms === 'function',
686
- mms: typeof mms === 'function',
687
- wms: typeof wms === 'function',
688
- })
689
- return !!(hasSupport && isDash)
613
+ return typeof ctor === 'function'
690
614
  }