@gcorevideo/player 2.17.0 → 2.18.0

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 (69) hide show
  1. package/dist/index.js +182 -109
  2. package/dist/player.d.ts +150 -24
  3. package/docs/api/player.containersize.md +19 -0
  4. package/docs/api/player.dashsettings.md +2 -0
  5. package/docs/api/player.errorlevel.md +1 -0
  6. package/docs/api/player.langtag.md +6 -0
  7. package/docs/api/player.md +57 -22
  8. package/docs/api/player.mediatransport.md +1 -0
  9. package/docs/api/player.playbackerror.code.md +2 -0
  10. package/docs/api/player.playbackerror.description.md +2 -0
  11. package/docs/api/player.playbackerror.level.md +2 -0
  12. package/docs/api/player.playbackerror.md +43 -4
  13. package/docs/api/player.playbackerror.message.md +2 -0
  14. package/docs/api/player.playbackerror.origin.md +21 -0
  15. package/docs/api/player.playbackerror.scope.md +16 -0
  16. package/docs/api/player.playbackerrorcode.md +4 -3
  17. package/docs/api/player.playbackmodule.md +1 -0
  18. package/docs/api/player.player.isdvrinuse.md +24 -0
  19. package/docs/api/player.player.isplaying.md +1 -1
  20. package/docs/api/player.player.md +15 -1
  21. package/docs/api/player.player.off.md +4 -4
  22. package/docs/api/player.player.on.md +5 -5
  23. package/docs/api/player.player.resize.md +3 -6
  24. package/docs/api/player.playercomponenttype.md +16 -0
  25. package/docs/api/player.playerdebugsettings.md +1 -1
  26. package/docs/api/player.playerdebugtag.md +1 -0
  27. package/docs/api/player.playerevent.md +96 -0
  28. package/docs/api/player.playereventhandler.md +3 -2
  29. package/docs/api/player.playereventparams.md +20 -0
  30. package/docs/api/player.playermediasourcedesc.md +1 -1
  31. package/docs/api/player.playermediasourcedesc.mimetype.md +1 -1
  32. package/docs/api/player.qualitylevel.bitrate.md +16 -0
  33. package/docs/api/player.qualitylevel.height.md +16 -0
  34. package/docs/api/player.qualitylevel.level.md +16 -0
  35. package/docs/api/player.qualitylevel.md +104 -7
  36. package/docs/api/player.qualitylevel.width.md +16 -0
  37. package/docs/api/player.timeposition.current.md +16 -0
  38. package/docs/api/player.timeposition.md +65 -7
  39. package/docs/api/player.timeposition.total.md +16 -0
  40. package/docs/api/player.timevalue.md +1 -1
  41. package/docs/api/player.translationsettings.md +7 -1
  42. package/docs/api/player.transportpreference.md +1 -0
  43. package/lib/Player.d.ts +30 -14
  44. package/lib/Player.d.ts.map +1 -1
  45. package/lib/Player.js +72 -65
  46. package/lib/internal.types.d.ts +3 -5
  47. package/lib/internal.types.d.ts.map +1 -1
  48. package/lib/playback/dash-playback/DashPlayback.d.ts +2 -0
  49. package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
  50. package/lib/playback/dash-playback/DashPlayback.js +37 -25
  51. package/lib/playback/hls-playback/HlsPlayback.d.ts +3 -0
  52. package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
  53. package/lib/playback/hls-playback/HlsPlayback.js +33 -18
  54. package/lib/playback.types.d.ts +65 -6
  55. package/lib/playback.types.d.ts.map +1 -1
  56. package/lib/playback.types.js +10 -0
  57. package/lib/types.d.ts +54 -5
  58. package/lib/types.d.ts.map +1 -1
  59. package/lib/types.js +31 -2
  60. package/package.json +1 -1
  61. package/src/Player.ts +109 -78
  62. package/src/internal.types.ts +3 -2
  63. package/src/playback/dash-playback/DashPlayback.ts +64 -35
  64. package/src/playback/hls-playback/HlsPlayback.ts +46 -22
  65. package/src/playback.types.ts +65 -5
  66. package/src/types.ts +56 -6
  67. package/temp/player.api.json +611 -87
  68. package/tsconfig.tsbuildinfo +1 -1
  69. package/dist/DashPlayback-BRJzl8D8.js +0 -901
package/src/Player.ts CHANGED
@@ -15,18 +15,33 @@ import type {
15
15
  CoreOptions,
16
16
  CorePluginOptions,
17
17
  } from './internal.types.js'
18
- import type { PlayerMediaSourceDesc, PlayerPlugin } from './types.js'
18
+ import type { ContainerSize, PlayerMediaSourceDesc, PlayerPlugin } from './types.js'
19
19
  import { PlayerConfig, PlayerEvent } from './types.js'
20
20
  import {
21
21
  buildMediaSourcesList,
22
22
  wrapSource,
23
23
  } from './utils/mediaSources.js'
24
24
  import { registerPlaybacks } from './playback/index.js'
25
+ import { PlaybackError, TimePosition } from './playback.types.js'
25
26
 
26
27
  /**
27
28
  * @beta
28
29
  */
29
- export type PlayerEventHandler<T extends PlayerEvent> = () => void
30
+ export type PlayerEventParams<E extends PlayerEvent> =
31
+ E extends PlayerEvent.Seek ? [number]
32
+ : E extends PlayerEvent.VolumeUpdate ? [number]
33
+ : E extends PlayerEvent.TimeUpdate ? [TimePosition]
34
+ : E extends PlayerEvent.Resize ? [{ width: number, height: number }]
35
+ : E extends PlayerEvent.Fullscreen ? [boolean]
36
+ : E extends PlayerEvent.Error ? [PlaybackError]
37
+ : []
38
+
39
+ /**
40
+ * Type of a listener callback function for a player event.
41
+ * See the description of the event parameters in {@link PlayerEvent}.
42
+ * @beta
43
+ */
44
+ export type PlayerEventHandler<E extends PlayerEvent> = (...args: PlayerEventParams<E>) => void
30
45
 
31
46
  const T = 'GPlayer'
32
47
 
@@ -42,6 +57,7 @@ const DEFAULT_OPTIONS: PlayerConfig = {
42
57
  }
43
58
 
44
59
  /**
60
+ * Module to perform the playback.
45
61
  * @beta
46
62
  */
47
63
  export type PlaybackModule = 'dash' | 'hls' | 'html5_video'
@@ -50,11 +66,11 @@ type PluginOptions = Record<string, unknown>
50
66
 
51
67
  /**
52
68
  * The main component to use in the application code.
69
+ * @beta
53
70
  * @remarks
54
71
  * The Player object provides very basic API to control playback.
55
72
  * To build a sophisticated UI, use the plugins framework to tap into the Clappr core.
56
73
  * {@link https://github.com/clappr/clappr/wiki/Architecture}
57
- * @beta
58
74
  */
59
75
  export class Player {
60
76
  private config: PlayerConfig = DEFAULT_OPTIONS
@@ -80,19 +96,19 @@ export class Player {
80
96
 
81
97
  /**
82
98
  * Adds a listener to a player event
83
- * @param event - See {@link PlayerEvent}
84
- * @param handler - See {@link PlayerEventHandler}
99
+ * @param event - event type, see {@link PlayerEvent}
100
+ * @param handler - a callback function to handle the event
85
101
  */
86
- on<T extends PlayerEvent>(event: T, handler: PlayerEventHandler<T>) {
102
+ on<E extends PlayerEvent>(event: E, handler: PlayerEventHandler<E>) {
87
103
  this.emitter.on(event, handler)
88
104
  }
89
105
 
90
106
  /**
91
107
  * Removes a previously added event listener
92
108
  * @param event - See {@link PlayerEvent}
93
- * @param handler - See {@link PlayerEventHandler}
109
+ * @param handler - a callback attached earlier to that event type
94
110
  */
95
- off<T extends PlayerEvent>(event: T, handler: PlayerEventHandler<T>) {
111
+ off<E extends PlayerEvent>(event: E, handler: PlayerEventHandler<E>) {
96
112
  this.emitter.off(event, handler)
97
113
  }
98
114
 
@@ -212,7 +228,16 @@ export class Player {
212
228
  }
213
229
 
214
230
  /**
215
- * Indicates the playing state of the player.
231
+ * Indicates whether DVR is in use.
232
+ * @remarks
233
+ * DVR mode, if it is enabled, is triggered we a user seeks behind the live edge.
234
+ */
235
+ isDvrInUse(): boolean {
236
+ return this.player?.isDvrInUse() ?? false
237
+ }
238
+
239
+ /**
240
+ * Indicates the playing state.
216
241
  */
217
242
  isPlaying(): boolean {
218
243
  return this.player?.isPlaying() ?? false
@@ -250,10 +275,10 @@ export class Player {
250
275
  * Resizes the player container element and everything within it.
251
276
  * @param newSize - new size of the player
252
277
  * @remarks
253
- * Use this method when the player itself does not detect the change in size of its container element.
278
+ * Use this method when the player itself does not detect properly the change in size of its container element.
254
279
  * It can be a case for orientation change on some mobile devices.
255
280
  */
256
- resize(newSize: { width: number; height: number }) {
281
+ resize(newSize: ContainerSize) {
257
282
  this.player?.resize(newSize)
258
283
  }
259
284
 
@@ -357,50 +382,15 @@ export class Player {
357
382
  }
358
383
  this.tunedIn = true
359
384
  const player = this.player
385
+
360
386
  this.bindContainerEventListeners(player)
361
387
  player.core.on(
362
388
  ClapprEvents.CORE_ACTIVE_CONTAINER_CHANGED,
363
389
  () => this.bindContainerEventListeners(player),
364
390
  null,
365
391
  )
366
- player.core.on(
367
- ClapprEvents.CORE_SCREEN_ORIENTATION_CHANGED,
368
- ({ orientation }: { orientation: 'landscape' | 'portrait' }) => {
369
- trace(`${T} CORE_SCREEN_ORIENTATION_CHANGED`, {
370
- orientation,
371
- rootNode: {
372
- width: this.rootNode?.clientWidth,
373
- height: this.rootNode?.clientHeight,
374
- },
375
- })
376
- if (Browser.isiOS && this.rootNode) {
377
- player.core.resize({
378
- width: this.rootNode.clientWidth,
379
- height: this.rootNode.clientHeight,
380
- })
381
- }
382
- },
383
- null,
384
- )
385
- player.core.on(
386
- ClapprEvents.CORE_RESIZE,
387
- ({ width, height }: { width: number; height: number }) => {
388
- trace(`${T} CORE_RESIZE`, {
389
- width,
390
- height,
391
- })
392
- },
393
- null,
394
- )
395
- player.core.on(
396
- ClapprEvents.CORE_FULLSCREEN,
397
- (isFullscreen: boolean) => {
398
- trace(`${T} CORE_FULLSCREEN`, {
399
- isFullscreen,
400
- })
401
- },
402
- null,
403
- )
392
+ this.bindSizeManagementListeners(player)
393
+
404
394
  if (this.config.autoPlay) {
405
395
  setTimeout(() => {
406
396
  trace(`${T} autoPlay`, {
@@ -418,6 +408,15 @@ export class Player {
418
408
  }
419
409
  }
420
410
 
411
+ private safeTriggerEvent<E extends PlayerEvent>(event: E, ...args: PlayerEventParams<E>) {
412
+ try {
413
+ this.emitter.emit(event, ...args)
414
+ } catch (e) {
415
+ reportError(e)
416
+ }
417
+ }
418
+
419
+ // TODO test
421
420
  private events: CorePlayerEvents = {
422
421
  onReady: () => {
423
422
  trace(`${T} onReady`, {
@@ -434,38 +433,29 @@ export class Player {
434
433
  // TODO ensure that CORE_ACTIVE_CONTAINER_CHANGED does not get caught before onReady
435
434
  setTimeout(() => this.tuneIn(), 0)
436
435
  },
437
- onResize: (newSize: { width: number; height: number }) => {
438
- trace(`${T} onResize`, {
439
- newSize,
440
- })
441
- },
442
436
  onPlay: () => {
443
- try {
444
- this.emitter.emit(PlayerEvent.Play)
445
- } catch (e) {
446
- reportError(e)
447
- }
437
+ this.safeTriggerEvent(PlayerEvent.Play)
448
438
  },
449
439
  onPause: () => {
450
- try {
451
- this.emitter.emit(PlayerEvent.Pause)
452
- } catch (e) {
453
- reportError(e)
454
- }
440
+ this.safeTriggerEvent(PlayerEvent.Pause)
455
441
  },
456
442
  onEnded: () => {
457
- try {
458
- this.emitter.emit(PlayerEvent.Ended)
459
- } catch (e) {
460
- reportError(e)
461
- }
443
+ this.safeTriggerEvent(PlayerEvent.Ended)
444
+ },
445
+ onSeek: (time: number) => {
446
+ this.safeTriggerEvent(PlayerEvent.Seek, time)
462
447
  },
463
448
  onStop: () => {
464
- try {
465
- this.emitter.emit(PlayerEvent.Stop)
466
- } catch (e) {
467
- reportError(e)
468
- }
449
+ this.safeTriggerEvent(PlayerEvent.Stop)
450
+ },
451
+ onVolumeUpdate: (volume: number) => {
452
+ this.safeTriggerEvent(PlayerEvent.VolumeUpdate, volume)
453
+ },
454
+ onTimeUpdate: (time: TimePosition) => {
455
+ this.safeTriggerEvent(PlayerEvent.TimeUpdate, time)
456
+ },
457
+ onError: (error: PlaybackError) => {
458
+ this.safeTriggerEvent(PlayerEvent.Error, error)
469
459
  },
470
460
  }
471
461
 
@@ -496,6 +486,7 @@ export class Player {
496
486
  mute: this.config.mute,
497
487
  crossOrigin: 'anonymous', // TODO
498
488
  hlsjsConfig: {
489
+ // TODO
499
490
  debug: this.config.debug === 'all' || this.config.debug === 'hls',
500
491
  },
501
492
  },
@@ -504,7 +495,7 @@ export class Player {
504
495
  width: rootNode.clientWidth,
505
496
  source: source ? source.source : undefined,
506
497
  mimeType: source ? source.mimeType : undefined,
507
- sources, // prevent Clappr from loading all sources simultaneously
498
+ sources,
508
499
  strings: this.config.strings,
509
500
  }
510
501
  return coreOptions
@@ -523,9 +514,6 @@ export class Player {
523
514
  }
524
515
 
525
516
  private bindContainerEventListeners(player: PlayerClappr) {
526
- trace(`${T} bindContainerEventListeners`, {
527
- activePlayback: player.core.activePlayback?.name,
528
- })
529
517
  if (Browser.isiOS && player.core.activePlayback) {
530
518
  player.core.activePlayback.$el.on('webkitendfullscreen', () => {
531
519
  try {
@@ -536,4 +524,47 @@ export class Player {
536
524
  })
537
525
  }
538
526
  }
527
+
528
+ private bindSizeManagementListeners(player: PlayerClappr) {
529
+ player.core.on(
530
+ ClapprEvents.CORE_SCREEN_ORIENTATION_CHANGED,
531
+ ({ orientation }: { orientation: 'landscape' | 'portrait' }) => {
532
+ trace(`${T} on CORE_SCREEN_ORIENTATION_CHANGED`, {
533
+ orientation,
534
+ rootNode: {
535
+ width: this.rootNode?.clientWidth,
536
+ height: this.rootNode?.clientHeight,
537
+ },
538
+ })
539
+ if (Browser.isiOS && this.rootNode) {
540
+ player.core.resize({
541
+ width: this.rootNode.clientWidth,
542
+ height: this.rootNode.clientHeight,
543
+ })
544
+ }
545
+ },
546
+ null,
547
+ )
548
+ player.core.on(
549
+ ClapprEvents.CORE_RESIZE,
550
+ ({ width, height }: { width: number; height: number }) => {
551
+ trace(`${T} on CORE_RESIZE`, {
552
+ width,
553
+ height,
554
+ })
555
+ this.safeTriggerEvent(PlayerEvent.Resize, { width, height })
556
+ },
557
+ null,
558
+ )
559
+ player.core.on(
560
+ ClapprEvents.CORE_FULLSCREEN,
561
+ (isFullscreen: boolean) => {
562
+ trace(`${T} CORE_FULLSCREEN`, {
563
+ isFullscreen,
564
+ })
565
+ this.safeTriggerEvent(PlayerEvent.Fullscreen, isFullscreen)
566
+ },
567
+ null,
568
+ )
569
+ }
539
570
  }
@@ -4,7 +4,7 @@ import type {
4
4
  Playback as ClapprPlayback,
5
5
  } from "@clappr/core";
6
6
 
7
- import { PlaybackType, PlayerDebugTag, PlayerMediaSource } from "./types";
7
+ import { ContainerSize, PlaybackType, PlayerDebugTag, PlayerMediaSource } from "./types";
8
8
  import { PlaybackError } from "./playback.types";
9
9
 
10
10
  type ExternalTrack = {
@@ -54,6 +54,7 @@ export interface CorePlaybackConfig {
54
54
  playInline: boolean;
55
55
  preload?: 'metadata' | 'auto' | 'none';
56
56
  // preferredAudioLanguage?: string;
57
+ recycleVideo?: boolean;
57
58
  shakaConfiguration?: ShakaConfig;
58
59
  }
59
60
 
@@ -63,7 +64,7 @@ export interface CorePlaybackConfig {
63
64
  export type CorePlayerEvents = {
64
65
  // TODO event arguments types
65
66
  onReady?: () => void;
66
- onResize?: (data: { width: number; height: number }) => void;
67
+ onResize?: (data: ContainerSize) => void;
67
68
  onPlay?: (metadata: unknown) => void;
68
69
  onPause?: (metadata: unknown) => void;
69
70
  onStop?: (metadata: unknown) => void;
@@ -2,7 +2,15 @@
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, PlayerError, Utils } from '@clappr/core'
5
+ import {
6
+ Events,
7
+ HTML5Video,
8
+ Log,
9
+ Playback,
10
+ PlayerError,
11
+ Utils,
12
+ $,
13
+ } from '@clappr/core'
6
14
  import { trace } from '@gcorevideo/utils'
7
15
  import assert from 'assert'
8
16
  import DASHJS, {
@@ -14,7 +22,15 @@ import DASHJS, {
14
22
  IManifestInfo,
15
23
  } from 'dashjs'
16
24
 
17
- import { PlaybackError, PlaybackErrorCode, QualityLevel, TimePosition, TimeUpdate, TimeValue } from '../../playback.types.js'
25
+ import {
26
+ PlaybackError,
27
+ PlaybackErrorCode,
28
+ PlayerComponentType,
29
+ QualityLevel,
30
+ TimePosition,
31
+ TimeUpdate,
32
+ TimeValue,
33
+ } from '../../playback.types.js'
18
34
  import { isDashSource } from '../../utils/mediaSources.js'
19
35
 
20
36
  const AUTO = -1
@@ -123,13 +139,15 @@ export default class DashPlayback extends HTML5Video {
123
139
  )
124
140
  const dash = this._dash
125
141
 
126
- // TODO use $.extend
127
- const settings = this.options.dash ? structuredClone(this.options.dash) : {}
128
- settings.streaming = settings.streaming || {}
129
- settings.streaming.abr = settings.streaming.abr || {}
130
- settings.streaming.abr.autoSwitchBitrate =
131
- settings.streaming.abr.autoSwitchBitrate || {}
132
- settings.streaming.abr.autoSwitchBitrate.video = id === -1
142
+ const settings = $.extend(true, {}, this.options.dash, {
143
+ streaming: {
144
+ abr: {
145
+ autoSwitchBitrate: {
146
+ video: id === -1,
147
+ },
148
+ },
149
+ },
150
+ })
133
151
 
134
152
  dash.updateSettings(settings)
135
153
  if (id !== -1) {
@@ -222,16 +240,14 @@ export default class DashPlayback extends HTML5Video {
222
240
 
223
241
  if (this.options.dash) {
224
242
  // TODO use $.extend
225
- const settings = structuredClone(this.options.dash)
226
- if (!settings.streaming) {
227
- settings.streaming = {}
228
- }
229
- if (!settings.streaming.text) {
230
- settings.streaming.text = {
231
- defaultEnabled: false,
232
- }
233
- }
234
- this._dash.updateSettings(this.options.dash)
243
+ const settings = $.extend({}, this.options.dash, {
244
+ streaming: {
245
+ text: {
246
+ defaultEnabled: false,
247
+ },
248
+ },
249
+ })
250
+ this._dash.updateSettings(settings)
235
251
  }
236
252
 
237
253
  this._dash.attachView(this.el)
@@ -250,20 +266,18 @@ export default class DashPlayback extends HTML5Video {
250
266
 
251
267
  this._updatePlaybackType()
252
268
  this._fillLevels(bitrates)
269
+
253
270
  dash.on(DASHJS.MediaPlayer.events.QUALITY_CHANGE_REQUESTED, (evt) => {
254
- // TODO
255
- assert.ok(
256
- this._levels,
257
- 'An array of levels is required to change quality',
258
- )
259
- const newLevel = this._levels.find(
260
- (level) => level.level === evt.newQuality,
261
- ) // TODO or simply this._levels[evt.newQuality]?
262
- assert.ok(newLevel, 'A valid level is required to change quality')
271
+ const newLevel = this.getLevel(evt.newQuality)
263
272
  this.onLevelSwitch(newLevel)
264
273
  })
265
274
  })
266
275
 
276
+ this._dash.on(DASHJS.MediaPlayer.events.QUALITY_CHANGE_RENDERED, (evt: DASHJS.QualityChangeRenderedEvent) => {
277
+ const currentLevel = this.getLevel(evt.newQuality)
278
+ this.onLevelSwitchEnd(currentLevel)
279
+ })
280
+
267
281
  this._dash.on(
268
282
  DASHJS.MediaPlayer.events.METRIC_ADDED,
269
283
  (e: DashMetricEvent) => {
@@ -409,7 +423,6 @@ export default class DashPlayback extends HTML5Video {
409
423
  trace(`${T} _onDASHJSSError`, { event })
410
424
  this._stopTimeUpdateTimer()
411
425
 
412
-
413
426
  // Note that the other error types are deprecated
414
427
  const e = (event as MediaPlayerErrorEvent).error
415
428
  switch (e.code) {
@@ -419,7 +432,7 @@ export default class DashPlayback extends HTML5Video {
419
432
  case DASHJS.MediaPlayer.errors.DOWNLOAD_ERROR_ID_MANIFEST_CODE:
420
433
  case DASHJS.MediaPlayer.errors.DOWNLOAD_ERROR_ID_CONTENT_CODE:
421
434
  case DASHJS.MediaPlayer.errors.DOWNLOAD_ERROR_ID_INITIALIZATION_CODE:
422
- // TODO these probably indicate a broken manifest and should be treated by removing the source
435
+ // TODO these probably indicate a broken manifest and should be treated by removing the source
423
436
  case DASHJS.MediaPlayer.errors.MANIFEST_ERROR_ID_NOSTREAMS_CODE:
424
437
  case DASHJS.MediaPlayer.errors.MANIFEST_ERROR_ID_PARSE_CODE:
425
438
  case DASHJS.MediaPlayer.errors.MANIFEST_ERROR_ID_MULTIPLEXED_CODE:
@@ -431,7 +444,7 @@ export default class DashPlayback extends HTML5Video {
431
444
  description: e.message,
432
445
  level: PlayerError.Levels.FATAL,
433
446
  })
434
- break;
447
+ break
435
448
  // TODO more cases
436
449
  default:
437
450
  this.triggerError({
@@ -443,9 +456,15 @@ export default class DashPlayback extends HTML5Video {
443
456
  }
444
457
  }
445
458
 
446
- private triggerError(error: PlaybackError) {
459
+ private triggerError(
460
+ error: Pick<PlaybackError, 'code' | 'message' | 'description' | 'level'>,
461
+ ) {
447
462
  trace(`${T} triggerError`, { error })
448
- this.trigger(Events.PLAYBACK_ERROR, error)
463
+ this.trigger(Events.PLAYBACK_ERROR, {
464
+ ...error,
465
+ origin: this.name,
466
+ scope: DashPlayback.type as PlayerComponentType,
467
+ })
449
468
  // only reset the dash player in 10ms async, so that the rest of the
450
469
  // calling function finishes
451
470
  setTimeout(() => {
@@ -591,9 +610,13 @@ export default class DashPlayback extends HTML5Video {
591
610
  private onLevelSwitch(currentLevel: QualityLevel) {
592
611
  // TODO check the two below
593
612
  this.trigger(Events.PLAYBACK_LEVEL_SWITCH, currentLevel)
613
+ }
614
+
615
+ private onLevelSwitchEnd(currentLevel: QualityLevel) {
594
616
  this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)
595
- const isHD = (currentLevel.height >= 720 || (currentLevel.bitrate / 1000) >= 2000);
596
- this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, isHD);
617
+ const isHD =
618
+ currentLevel.height >= 720 || currentLevel.bitrate / 1000 >= 2000
619
+ this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, isHD)
597
620
  this.trigger(Events.PLAYBACK_BITRATE, currentLevel)
598
621
  }
599
622
 
@@ -604,6 +627,12 @@ export default class DashPlayback extends HTML5Video {
604
627
  isSeekEnabled() {
605
628
  return this._playbackType === Playback.VOD || this.dvrEnabled
606
629
  }
630
+
631
+ private getLevel(quality: number) {
632
+ const ret = this.levels.find((level) => level.level === quality)
633
+ assert.ok(ret, 'Invalid quality level')
634
+ return ret
635
+ }
607
636
  }
608
637
 
609
638
  DashPlayback.canPlay = function (resource, mimeType) {
@@ -28,6 +28,7 @@ import HLSJS, {
28
28
  import {
29
29
  PlaybackError,
30
30
  PlaybackErrorCode,
31
+ PlayerComponentType,
31
32
  QualityLevel,
32
33
  TimePosition,
33
34
  TimeUpdate,
@@ -426,6 +427,11 @@ export default class HlsPlayback extends HTML5Video {
426
427
  (evt: HlsEvents.LEVEL_SWITCHING, data: LevelSwitchingData) =>
427
428
  this._onLevelSwitch(evt, data),
428
429
  )
430
+ this._hls.on(
431
+ HLSJS.Events.LEVEL_SWITCHED,
432
+ (evt: HlsEvents.LEVEL_SWITCHED, data: { level: number }) =>
433
+ this._onLevelSwitched(evt, data),
434
+ )
429
435
  this._hls.on(
430
436
  HLSJS.Events.FRAG_CHANGED,
431
437
  (evt: HlsEvents.FRAG_CHANGED, data: FragChangedData) =>
@@ -511,7 +517,10 @@ export default class HlsPlayback extends HTML5Video {
511
517
  this._hls.recoverMediaError()
512
518
  } else {
513
519
  Log.error('hlsjs: failed to recover', { evt, data })
514
- trace(`${T} _recover failed to recover`, { type: data.type, details: data.details })
520
+ trace(`${T} _recover failed to recover`, {
521
+ type: data.type,
522
+ details: data.details,
523
+ })
515
524
  error.level = PlayerError.Levels.FATAL
516
525
 
517
526
  this.triggerError(error)
@@ -614,12 +623,18 @@ export default class HlsPlayback extends HTML5Video {
614
623
  }
615
624
 
616
625
  _onHLSJSError(evt: HlsEvents.ERROR, data: HlsErrorData) {
617
- trace(`${T} _onHLSJSError`, { fatal: data.fatal, type: data.type, details: data.details })
626
+ trace(`${T} _onHLSJSError`, {
627
+ fatal: data.fatal,
628
+ type: data.type,
629
+ details: data.details,
630
+ })
618
631
  const error: PlaybackError = {
619
632
  code: PlaybackErrorCode.Generic,
620
633
  description: `${this.name} error: type: ${data.type}, details: ${data.details} fatal: ${data.fatal}`,
621
634
  level: data.fatal ? PlayerError.Levels.FATAL : PlayerError.Levels.WARN,
622
635
  message: `${this.name} error: type: ${data.type}, details: ${data.details}`,
636
+ origin: this.name,
637
+ scope: HlsPlayback.type as PlayerComponentType,
623
638
  }
624
639
 
625
640
  if (data.response) {
@@ -657,9 +672,12 @@ export default class HlsPlayback extends HTML5Video {
657
672
  evt,
658
673
  data,
659
674
  })
660
- trace(`${T} _onHLSJSError trying to recover from network error`, {
661
- details: data.details,
662
- })
675
+ trace(
676
+ `${T} _onHLSJSError trying to recover from network error`,
677
+ {
678
+ details: data.details,
679
+ },
680
+ )
663
681
  error.level = PlayerError.Levels.WARN
664
682
  this._hls?.startLoad()
665
683
  break
@@ -705,7 +723,10 @@ export default class HlsPlayback extends HTML5Video {
705
723
  }
706
724
 
707
725
  Log.warn('hlsjs: non-fatal error occurred', { evt, data })
708
- trace(`${T} _onHLSJSError non-fatal error occurred`, { type: data.type, details: data.details })
726
+ trace(`${T} _onHLSJSError non-fatal error occurred`, {
727
+ type: data.type,
728
+ details: data.details,
729
+ })
709
730
  }
710
731
  }
711
732
 
@@ -1035,23 +1056,26 @@ export default class HlsPlayback extends HTML5Video {
1035
1056
  if (!this.levels.length) {
1036
1057
  this._fillLevels()
1037
1058
  }
1038
- this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)
1039
1059
  this.trigger(Events.PLAYBACK_LEVEL_SWITCH, data)
1040
- assert(this._hls, 'Hls.js instance is not available')
1041
- const currentLevel = this._hls.levels[data.level]
1042
-
1043
- if (currentLevel) {
1044
- // TODO should highDefinition be private and maybe have a read only accessor if it's used somewhere
1045
- this.highDefinition =
1046
- currentLevel.height >= 720 || currentLevel.bitrate / 1000 >= 2000
1047
- this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, this.highDefinition)
1048
- this.trigger(Events.PLAYBACK_BITRATE, {
1049
- height: currentLevel.height,
1050
- width: currentLevel.width,
1051
- bitrate: currentLevel.bitrate,
1052
- level: data.level,
1053
- })
1054
- }
1060
+ }
1061
+
1062
+ _onLevelSwitched(evt: HlsEvents.LEVEL_SWITCHED, data: { level: number }) {
1063
+ // @ts-ignore
1064
+ const currentLevel = this._hls.levels[data.level] // TODO or find by .id == level?
1065
+ assert.ok(currentLevel, 'Invalid quality level')
1066
+ this._currentLevel = data.level
1067
+
1068
+ // TODO should highDefinition be private and maybe have a read only accessor if it's used somewhere
1069
+ this.highDefinition =
1070
+ currentLevel.height >= 720 || currentLevel.bitrate / 1000 >= 2000
1071
+ this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, this.highDefinition)
1072
+ this.trigger(Events.PLAYBACK_BITRATE, {
1073
+ height: currentLevel.height,
1074
+ width: currentLevel.width,
1075
+ bitrate: currentLevel.bitrate,
1076
+ level: data.level,
1077
+ })
1078
+ this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)
1055
1079
  }
1056
1080
 
1057
1081
  get dvrEnabled() {