hls.js 1.5.13 → 1.5.14-0.canary.10417

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 (103) hide show
  1. package/README.md +4 -3
  2. package/dist/hls-demo.js +41 -38
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +4211 -2666
  5. package/dist/hls.js.d.ts +179 -110
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +2841 -1921
  8. package/dist/hls.light.js.map +1 -1
  9. package/dist/hls.light.min.js +1 -1
  10. package/dist/hls.light.min.js.map +1 -1
  11. package/dist/hls.light.mjs +2569 -1639
  12. package/dist/hls.light.mjs.map +1 -1
  13. package/dist/hls.min.js +1 -1
  14. package/dist/hls.min.js.map +1 -1
  15. package/dist/hls.mjs +3572 -2017
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +38 -38
  20. package/src/config.ts +5 -2
  21. package/src/controller/abr-controller.ts +39 -25
  22. package/src/controller/audio-stream-controller.ts +156 -136
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +27 -10
  25. package/src/controller/base-stream-controller.ts +234 -89
  26. package/src/controller/buffer-controller.ts +250 -97
  27. package/src/controller/buffer-operation-queue.ts +16 -19
  28. package/src/controller/cap-level-controller.ts +3 -2
  29. package/src/controller/cmcd-controller.ts +51 -14
  30. package/src/controller/content-steering-controller.ts +29 -15
  31. package/src/controller/eme-controller.ts +10 -23
  32. package/src/controller/error-controller.ts +28 -22
  33. package/src/controller/fps-controller.ts +8 -3
  34. package/src/controller/fragment-finders.ts +44 -16
  35. package/src/controller/fragment-tracker.ts +58 -25
  36. package/src/controller/gap-controller.ts +43 -16
  37. package/src/controller/id3-track-controller.ts +45 -35
  38. package/src/controller/latency-controller.ts +18 -13
  39. package/src/controller/level-controller.ts +37 -19
  40. package/src/controller/stream-controller.ts +100 -83
  41. package/src/controller/subtitle-stream-controller.ts +35 -47
  42. package/src/controller/subtitle-track-controller.ts +5 -3
  43. package/src/controller/timeline-controller.ts +20 -22
  44. package/src/crypt/aes-crypto.ts +21 -2
  45. package/src/crypt/decrypter-aes-mode.ts +4 -0
  46. package/src/crypt/decrypter.ts +32 -16
  47. package/src/crypt/fast-aes-key.ts +28 -5
  48. package/src/demux/audio/aacdemuxer.ts +2 -2
  49. package/src/demux/audio/ac3-demuxer.ts +4 -3
  50. package/src/demux/audio/adts.ts +9 -4
  51. package/src/demux/audio/base-audio-demuxer.ts +16 -14
  52. package/src/demux/audio/mp3demuxer.ts +4 -3
  53. package/src/demux/audio/mpegaudio.ts +1 -1
  54. package/src/demux/mp4demuxer.ts +7 -7
  55. package/src/demux/sample-aes.ts +2 -0
  56. package/src/demux/transmuxer-interface.ts +8 -16
  57. package/src/demux/transmuxer-worker.ts +4 -4
  58. package/src/demux/transmuxer.ts +16 -3
  59. package/src/demux/tsdemuxer.ts +75 -38
  60. package/src/demux/video/avc-video-parser.ts +210 -121
  61. package/src/demux/video/base-video-parser.ts +135 -2
  62. package/src/demux/video/exp-golomb.ts +0 -208
  63. package/src/demux/video/hevc-video-parser.ts +749 -0
  64. package/src/events.ts +8 -1
  65. package/src/exports-named.ts +1 -1
  66. package/src/hls.ts +84 -47
  67. package/src/loader/date-range.ts +71 -5
  68. package/src/loader/fragment-loader.ts +23 -21
  69. package/src/loader/fragment.ts +8 -4
  70. package/src/loader/key-loader.ts +3 -1
  71. package/src/loader/level-details.ts +6 -6
  72. package/src/loader/level-key.ts +10 -9
  73. package/src/loader/m3u8-parser.ts +138 -144
  74. package/src/loader/playlist-loader.ts +5 -7
  75. package/src/remux/mp4-generator.ts +196 -1
  76. package/src/remux/mp4-remuxer.ts +32 -62
  77. package/src/remux/passthrough-remuxer.ts +1 -1
  78. package/src/task-loop.ts +5 -2
  79. package/src/types/component-api.ts +3 -1
  80. package/src/types/demuxer.ts +3 -0
  81. package/src/types/events.ts +19 -6
  82. package/src/types/fragment-tracker.ts +2 -2
  83. package/src/types/media-playlist.ts +9 -1
  84. package/src/types/remuxer.ts +1 -1
  85. package/src/utils/attr-list.ts +96 -9
  86. package/src/utils/buffer-helper.ts +12 -31
  87. package/src/utils/cea-608-parser.ts +1 -3
  88. package/src/utils/codecs.ts +34 -5
  89. package/src/utils/encryption-methods-util.ts +21 -0
  90. package/src/utils/fetch-loader.ts +1 -1
  91. package/src/utils/hash.ts +10 -0
  92. package/src/utils/hdr.ts +4 -7
  93. package/src/utils/imsc1-ttml-parser.ts +1 -1
  94. package/src/utils/keysystem-util.ts +1 -6
  95. package/src/utils/level-helper.ts +71 -44
  96. package/src/utils/logger.ts +58 -23
  97. package/src/utils/mp4-tools.ts +5 -3
  98. package/src/utils/rendition-helper.ts +100 -74
  99. package/src/utils/utf8-utils.ts +18 -0
  100. package/src/utils/variable-substitution.ts +0 -19
  101. package/src/utils/webvtt-parser.ts +2 -12
  102. package/src/demux/id3.ts +0 -411
  103. package/src/types/general.ts +0 -6
package/src/events.ts CHANGED
@@ -1,8 +1,9 @@
1
- import {
1
+ import type {
2
2
  ManifestLoadedData,
3
3
  ManifestLoadingData,
4
4
  MediaAttachedData,
5
5
  MediaAttachingData,
6
+ MediaEndedData,
6
7
  LevelLoadingData,
7
8
  LevelLoadedData,
8
9
  ManifestParsedData,
@@ -60,6 +61,8 @@ export enum Events {
60
61
  MEDIA_DETACHING = 'hlsMediaDetaching',
61
62
  // Fired when MediaSource has been detached from media element
62
63
  MEDIA_DETACHED = 'hlsMediaDetached',
64
+ // Fired when HTMLMediaElement dispatches "ended" event, or stalls at end of VOD program
65
+ MEDIA_ENDED = 'hlsMediaEnded',
63
66
  // Fired when the buffer is going to be reset
64
67
  BUFFER_RESET = 'hlsBufferReset',
65
68
  // Fired when we know about the codecs that we need buffers for to push into - data: {tracks : { container, codec, levelCodec, initSegment, metadata }}
@@ -184,6 +187,10 @@ export interface HlsListeners {
184
187
  ) => void;
185
188
  [Events.MEDIA_DETACHING]: (event: Events.MEDIA_DETACHING) => void;
186
189
  [Events.MEDIA_DETACHED]: (event: Events.MEDIA_DETACHED) => void;
190
+ [Events.MEDIA_ENDED]: (
191
+ event: Events.MEDIA_ENDED,
192
+ data: MediaEndedData,
193
+ ) => void;
187
194
  [Events.BUFFER_RESET]: (event: Events.BUFFER_RESET) => void;
188
195
  [Events.BUFFER_CODECS]: (
189
196
  event: Events.BUFFER_CODECS,
@@ -1,7 +1,7 @@
1
1
  import Hls from './hls';
2
2
  import { Events } from './events';
3
3
  import { ErrorTypes, ErrorDetails } from './errors';
4
- import { Level } from './types/level';
4
+ import type { Level } from './types/level';
5
5
  import AbrController from './controller/abr-controller';
6
6
  import AudioTrackController from './controller/audio-track-controller';
7
7
  import AudioStreamController from './controller/audio-stream-controller';
package/src/hls.ts CHANGED
@@ -8,7 +8,7 @@ import KeyLoader from './loader/key-loader';
8
8
  import StreamController from './controller/stream-controller';
9
9
  import { isMSESupported, isSupported } from './is-supported';
10
10
  import { getMediaSource } from './utils/mediasource-helper';
11
- import { logger, enableLogs } from './utils/logger';
11
+ import { enableLogs, type ILogger } from './utils/logger';
12
12
  import { enableStreamingMode, hlsDefaultConfig, mergeConfig } from './config';
13
13
  import { EventEmitter } from 'eventemitter3';
14
14
  import { Events } from './events';
@@ -59,9 +59,13 @@ export default class Hls implements HlsEventEmitter {
59
59
  */
60
60
  public readonly userConfig: Partial<HlsConfig>;
61
61
 
62
+ /**
63
+ * The logger functions used by this player instance, configured on player instantiation.
64
+ */
65
+ public readonly logger: ILogger;
66
+
62
67
  private coreComponents: ComponentAPI[];
63
68
  private networkControllers: NetworkComponentAPI[];
64
- private started: boolean = false;
65
69
  private _emitter: HlsEventEmitter = new EventEmitter();
66
70
  private _autoLevelCapping: number = -1;
67
71
  private _maxHdcpLevel: HdcpLevel = null;
@@ -76,7 +80,7 @@ export default class Hls implements HlsEventEmitter {
76
80
  private emeController: EMEController;
77
81
  private cmcdController: CMCDController;
78
82
  private _media: HTMLMediaElement | null = null;
79
- private url: string | null = null;
83
+ private _url: string | null = null;
80
84
  private triggeringException?: boolean;
81
85
 
82
86
  /**
@@ -142,12 +146,19 @@ export default class Hls implements HlsEventEmitter {
142
146
  * @param userConfig - Configuration options applied over `Hls.DefaultConfig`
143
147
  */
144
148
  constructor(userConfig: Partial<HlsConfig> = {}) {
145
- enableLogs(userConfig.debug || false, 'Hls instance');
146
- const config = (this.config = mergeConfig(Hls.DefaultConfig, userConfig));
149
+ const logger = (this.logger = enableLogs(
150
+ userConfig.debug || false,
151
+ 'Hls instance',
152
+ ));
153
+ const config = (this.config = mergeConfig(
154
+ Hls.DefaultConfig,
155
+ userConfig,
156
+ logger,
157
+ ));
147
158
  this.userConfig = userConfig;
148
159
 
149
160
  if (config.progressive) {
150
- enableStreamingMode(config);
161
+ enableStreamingMode(config, logger);
151
162
  }
152
163
 
153
164
  // core controllers and network loaders
@@ -160,8 +171,10 @@ export default class Hls implements HlsEventEmitter {
160
171
  } = config;
161
172
  const errorController = new ConfigErrorController(this);
162
173
  const abrController = (this.abrController = new ConfigAbrController(this));
174
+ // FragmentTracker must be defined before StreamController because the order of event handling is important
175
+ const fragmentTracker = new FragmentTracker(this);
163
176
  const bufferController = (this.bufferController =
164
- new ConfigBufferController(this));
177
+ new ConfigBufferController(this, fragmentTracker));
165
178
  const capLevelController = (this.capLevelController =
166
179
  new ConfigCapLevelController(this));
167
180
 
@@ -170,7 +183,7 @@ export default class Hls implements HlsEventEmitter {
170
183
  const id3TrackController = new ID3TrackController(this);
171
184
 
172
185
  const ConfigContentSteeringController = config.contentSteeringController;
173
- // ConentSteeringController is defined before LevelController to receive Multivariant Playlist events first
186
+ // ContentSteeringController is defined before LevelController to receive Multivariant Playlist events first
174
187
  const contentSteering = ConfigContentSteeringController
175
188
  ? new ConfigContentSteeringController(this)
176
189
  : null;
@@ -178,8 +191,6 @@ export default class Hls implements HlsEventEmitter {
178
191
  this,
179
192
  contentSteering,
180
193
  ));
181
- // FragmentTracker must be defined before StreamController because the order of event handling is important
182
- const fragmentTracker = new FragmentTracker(this);
183
194
  const keyLoader = new KeyLoader(this.config);
184
195
  const streamController = (this.streamController = new StreamController(
185
196
  this,
@@ -221,7 +232,7 @@ export default class Hls implements HlsEventEmitter {
221
232
  new AudioStreamControllerClass(this, fragmentTracker, keyLoader),
222
233
  );
223
234
  }
224
- // subtitleTrackController must be defined before subtitleStreamController because the order of event handling is important
235
+ // Instantiate subtitleTrackController before SubtitleStreamController to receive level events first
225
236
  this.subtitleTrackController = this.createController(
226
237
  config.subtitleTrackController,
227
238
  networkControllers,
@@ -320,7 +331,7 @@ export default class Hls implements HlsEventEmitter {
320
331
  try {
321
332
  return this.emit(event, event, eventObject);
322
333
  } catch (error) {
323
- logger.error(
334
+ this.logger.error(
324
335
  'An internal error happened while handling event ' +
325
336
  event +
326
337
  '. Error message: "' +
@@ -354,12 +365,12 @@ export default class Hls implements HlsEventEmitter {
354
365
  * Dispose of the instance
355
366
  */
356
367
  destroy() {
357
- logger.log('destroy');
368
+ this.logger.log('destroy');
358
369
  this.trigger(Events.DESTROYING, undefined);
359
370
  this.detachMedia();
360
371
  this.removeAllListeners();
361
372
  this._autoLevelCapping = -1;
362
- this.url = null;
373
+ this._url = null;
363
374
 
364
375
  this.networkControllers.forEach((component) => component.destroy());
365
376
  this.networkControllers.length = 0;
@@ -377,7 +388,7 @@ export default class Hls implements HlsEventEmitter {
377
388
  * Attaches Hls.js to a media element
378
389
  */
379
390
  attachMedia(media: HTMLMediaElement) {
380
- logger.log('attachMedia');
391
+ this.logger.log('attachMedia');
381
392
  this._media = media;
382
393
  this.trigger(Events.MEDIA_ATTACHING, { media: media });
383
394
  }
@@ -386,7 +397,7 @@ export default class Hls implements HlsEventEmitter {
386
397
  * Detach Hls.js from the media
387
398
  */
388
399
  detachMedia() {
389
- logger.log('detachMedia');
400
+ this.logger.log('detachMedia');
390
401
  this.trigger(Events.MEDIA_DETACHING, undefined);
391
402
  this._media = null;
392
403
  }
@@ -397,8 +408,8 @@ export default class Hls implements HlsEventEmitter {
397
408
  loadSource(url: string) {
398
409
  this.stopLoad();
399
410
  const media = this.media;
400
- const loadedSource = this.url;
401
- const loadingSource = (this.url = buildAbsoluteURL(
411
+ const loadedSource = this._url;
412
+ const loadingSource = (this._url = buildAbsoluteURL(
402
413
  self.location.href,
403
414
  url,
404
415
  {
@@ -407,7 +418,7 @@ export default class Hls implements HlsEventEmitter {
407
418
  ));
408
419
  this._autoLevelCapping = -1;
409
420
  this._maxHdcpLevel = null;
410
- logger.log(`loadSource:${loadingSource}`);
421
+ this.logger.log(`loadSource:${loadingSource}`);
411
422
  if (
412
423
  media &&
413
424
  loadedSource &&
@@ -420,6 +431,13 @@ export default class Hls implements HlsEventEmitter {
420
431
  this.trigger(Events.MANIFEST_LOADING, { url: url });
421
432
  }
422
433
 
434
+ /**
435
+ * Gets the currently loaded URL
436
+ */
437
+ public get url(): string | null {
438
+ return this._url;
439
+ }
440
+
423
441
  /**
424
442
  * Start loading data from the stream source.
425
443
  * Depending on default config, client starts loading automatically when a source is set.
@@ -428,8 +446,7 @@ export default class Hls implements HlsEventEmitter {
428
446
  * Defaults to -1 (None: starts from earliest point)
429
447
  */
430
448
  startLoad(startPosition: number = -1) {
431
- logger.log(`startLoad(${startPosition})`);
432
- this.started = true;
449
+ this.logger.log(`startLoad(${startPosition})`);
433
450
  this.networkControllers.forEach((controller) => {
434
451
  controller.startLoad(startPosition);
435
452
  });
@@ -439,34 +456,31 @@ export default class Hls implements HlsEventEmitter {
439
456
  * Stop loading of any stream data.
440
457
  */
441
458
  stopLoad() {
442
- logger.log('stopLoad');
443
- this.started = false;
459
+ this.logger.log('stopLoad');
444
460
  this.networkControllers.forEach((controller) => {
445
461
  controller.stopLoad();
446
462
  });
447
463
  }
448
464
 
449
465
  /**
450
- * Resumes stream controller segment loading if previously started.
466
+ * Resumes stream controller segment loading after `pauseBuffering` has been called.
451
467
  */
452
468
  resumeBuffering() {
453
- if (this.started) {
454
- this.networkControllers.forEach((controller) => {
455
- if ('fragmentLoader' in controller) {
456
- controller.startLoad(-1);
457
- }
458
- });
459
- }
469
+ this.networkControllers.forEach((controller) => {
470
+ if (controller.resumeBuffering) {
471
+ controller.resumeBuffering();
472
+ }
473
+ });
460
474
  }
461
475
 
462
476
  /**
463
- * Stops stream controller segment loading without changing 'started' state like stopLoad().
477
+ * Prevents stream controller from loading new segments until `resumeBuffering` is called.
464
478
  * This allows for media buffering to be paused without interupting playlist loading.
465
479
  */
466
480
  pauseBuffering() {
467
481
  this.networkControllers.forEach((controller) => {
468
- if ('fragmentLoader' in controller) {
469
- controller.stopLoad();
482
+ if (controller.pauseBuffering) {
483
+ controller.pauseBuffering();
470
484
  }
471
485
  });
472
486
  }
@@ -475,7 +489,7 @@ export default class Hls implements HlsEventEmitter {
475
489
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
476
490
  */
477
491
  swapAudioCodec() {
478
- logger.log('swapAudioCodec');
492
+ this.logger.log('swapAudioCodec');
479
493
  this.streamController.swapAudioCodec();
480
494
  }
481
495
 
@@ -486,7 +500,7 @@ export default class Hls implements HlsEventEmitter {
486
500
  * Automatic recovery of media-errors by this process is configurable.
487
501
  */
488
502
  recoverMediaError() {
489
- logger.log('recoverMediaError');
503
+ this.logger.log('recoverMediaError');
490
504
  const media = this._media;
491
505
  this.detachMedia();
492
506
  if (media) {
@@ -517,7 +531,7 @@ export default class Hls implements HlsEventEmitter {
517
531
  * Set quality level index immediately. This will flush the current buffer to replace the quality asap. That means playback will interrupt at least shortly to re-buffer and re-sync eventually. Set to -1 for automatic level selection.
518
532
  */
519
533
  set currentLevel(newLevel: number) {
520
- logger.log(`set currentLevel:${newLevel}`);
534
+ this.logger.log(`set currentLevel:${newLevel}`);
521
535
  this.levelController.manualLevel = newLevel;
522
536
  this.streamController.immediateLevelSwitch();
523
537
  }
@@ -536,7 +550,7 @@ export default class Hls implements HlsEventEmitter {
536
550
  * @param newLevel - Pass -1 for automatic level selection
537
551
  */
538
552
  set nextLevel(newLevel: number) {
539
- logger.log(`set nextLevel:${newLevel}`);
553
+ this.logger.log(`set nextLevel:${newLevel}`);
540
554
  this.levelController.manualLevel = newLevel;
541
555
  this.streamController.nextLevelSwitch();
542
556
  }
@@ -555,7 +569,7 @@ export default class Hls implements HlsEventEmitter {
555
569
  * @param newLevel - Pass -1 for automatic level selection
556
570
  */
557
571
  set loadLevel(newLevel: number) {
558
- logger.log(`set loadLevel:${newLevel}`);
572
+ this.logger.log(`set loadLevel:${newLevel}`);
559
573
  this.levelController.manualLevel = newLevel;
560
574
  }
561
575
 
@@ -586,7 +600,7 @@ export default class Hls implements HlsEventEmitter {
586
600
  * Sets "first-level", see getter.
587
601
  */
588
602
  set firstLevel(newLevel: number) {
589
- logger.log(`set firstLevel:${newLevel}`);
603
+ this.logger.log(`set firstLevel:${newLevel}`);
590
604
  this.levelController.firstLevel = newLevel;
591
605
  }
592
606
 
@@ -611,7 +625,7 @@ export default class Hls implements HlsEventEmitter {
611
625
  * (determined from download of first segment)
612
626
  */
613
627
  set startLevel(newLevel: number) {
614
- logger.log(`set startLevel:${newLevel}`);
628
+ this.logger.log(`set startLevel:${newLevel}`);
615
629
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
616
630
  if (newLevel !== -1) {
617
631
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -686,7 +700,7 @@ export default class Hls implements HlsEventEmitter {
686
700
  */
687
701
  set autoLevelCapping(newLevel: number) {
688
702
  if (this._autoLevelCapping !== newLevel) {
689
- logger.log(`set autoLevelCapping:${newLevel}`);
703
+ this.logger.log(`set autoLevelCapping:${newLevel}`);
690
704
  this._autoLevelCapping = newLevel;
691
705
  this.levelController.checkMaxAutoUpdated();
692
706
  }
@@ -795,6 +809,10 @@ export default class Hls implements HlsEventEmitter {
795
809
  return this.streamController.getMainFwdBufferInfo();
796
810
  }
797
811
 
812
+ public get maxBufferLength(): number {
813
+ return this.streamController.maxBufferLength;
814
+ }
815
+
798
816
  /**
799
817
  * Find and select the best matching audio track, making a level switch when a Group change is necessary.
800
818
  * Updates `hls.config.audioPreference`. Returns the selected track, or null when no matching track is found.
@@ -802,7 +820,7 @@ export default class Hls implements HlsEventEmitter {
802
820
  public setAudioOption(
803
821
  audioOption: MediaPlaylist | AudioSelectionOption | undefined,
804
822
  ): MediaPlaylist | null {
805
- return this.audioTrackController?.setAudioOption(audioOption);
823
+ return this.audioTrackController?.setAudioOption(audioOption) || null;
806
824
  }
807
825
  /**
808
826
  * Find and select the best matching subtitle track, making a level switch when a Group change is necessary.
@@ -811,8 +829,9 @@ export default class Hls implements HlsEventEmitter {
811
829
  public setSubtitleOption(
812
830
  subtitleOption: MediaPlaylist | SubtitleSelectionOption | undefined,
813
831
  ): MediaPlaylist | null {
814
- this.subtitleTrackController?.setSubtitleOption(subtitleOption);
815
- return null;
832
+ return (
833
+ this.subtitleTrackController?.setSubtitleOption(subtitleOption) || null
834
+ );
816
835
  }
817
836
 
818
837
  /**
@@ -957,6 +976,10 @@ export default class Hls implements HlsEventEmitter {
957
976
  return this.latencyController.targetLatency;
958
977
  }
959
978
 
979
+ set targetLatency(latency: number) {
980
+ this.latencyController.targetLatency = latency;
981
+ }
982
+
960
983
  /**
961
984
  * the rate at which the edge of the current live playlist is advancing or 1 if there is none
962
985
  */
@@ -970,6 +993,17 @@ export default class Hls implements HlsEventEmitter {
970
993
  get forceStartLoad(): boolean {
971
994
  return this.streamController.forceStartLoad;
972
995
  }
996
+
997
+ /**
998
+ * ContentSteering pathwayPriority getter/setter
999
+ */
1000
+ get pathwayPriority(): string[] | null {
1001
+ return this.levelController.pathwayPriority;
1002
+ }
1003
+
1004
+ set pathwayPriority(pathwayPriority: string[]) {
1005
+ this.levelController.pathwayPriority = pathwayPriority;
1006
+ }
973
1007
  }
974
1008
 
975
1009
  export type {
@@ -1032,7 +1066,7 @@ export type {
1032
1066
  TSDemuxerConfig,
1033
1067
  } from './config';
1034
1068
  export type { MediaKeySessionContext } from './controller/eme-controller';
1035
- export type { ILogger } from './utils/logger';
1069
+ export type { ILogger, Logger } from './utils/logger';
1036
1070
  export type {
1037
1071
  PathwayClone,
1038
1072
  SteeringManifest,
@@ -1046,7 +1080,7 @@ export type {
1046
1080
  KeySystems,
1047
1081
  KeySystemFormats,
1048
1082
  } from './utils/mediakeys-helper';
1049
- export type { DateRange } from './loader/date-range';
1083
+ export type { DateRange, DateRangeCue } from './loader/date-range';
1050
1084
  export type { LoadStats } from './loader/load-stats';
1051
1085
  export type { LevelKey } from './loader/level-key';
1052
1086
  export type { LevelDetails } from './loader/level-details';
@@ -1096,6 +1130,7 @@ export type { ChunkMetadata } from './types/transmuxer';
1096
1130
  export type {
1097
1131
  BaseSegment,
1098
1132
  Fragment,
1133
+ MediaFragment,
1099
1134
  Part,
1100
1135
  ElementaryStreams,
1101
1136
  ElementaryStreamTypes,
@@ -1147,6 +1182,7 @@ export type {
1147
1182
  ManifestParsedData,
1148
1183
  MediaAttachedData,
1149
1184
  MediaAttachingData,
1185
+ MediaEndedData,
1150
1186
  NonNativeTextTrack,
1151
1187
  NonNativeTextTracksData,
1152
1188
  SteeringManifestLoadedData,
@@ -1161,3 +1197,4 @@ export type {
1161
1197
  IErrorAction,
1162
1198
  } from './controller/error-controller';
1163
1199
  export type { AttrList } from './utils/attr-list';
1200
+ export type { ParsedMultivariantPlaylist } from './loader/m3u8-parser';
@@ -1,10 +1,12 @@
1
1
  import { AttrList } from '../utils/attr-list';
2
2
  import { logger } from '../utils/logger';
3
+ import type { Fragment } from './fragment';
3
4
 
4
5
  // Avoid exporting const enum so that these values can be inlined
5
6
  const enum DateRangeAttribute {
6
7
  ID = 'ID',
7
8
  CLASS = 'CLASS',
9
+ CUE = 'CUE',
8
10
  START_DATE = 'START-DATE',
9
11
  DURATION = 'DURATION',
10
12
  END_DATE = 'END-DATE',
@@ -12,12 +14,22 @@ const enum DateRangeAttribute {
12
14
  PLANNED_DURATION = 'PLANNED-DURATION',
13
15
  SCTE35_OUT = 'SCTE35-OUT',
14
16
  SCTE35_IN = 'SCTE35-IN',
17
+ SCTE35_CMD = 'SCTE35-CMD',
15
18
  }
16
19
 
20
+ export type DateRangeCue = {
21
+ pre: boolean;
22
+ post: boolean;
23
+ once: boolean;
24
+ };
25
+
26
+ const CLASS_INTERSTITIAL = 'com.apple.hls.interstitial';
27
+
17
28
  export function isDateRangeCueAttribute(attrName: string): boolean {
18
29
  return (
19
30
  attrName !== DateRangeAttribute.ID &&
20
31
  attrName !== DateRangeAttribute.CLASS &&
32
+ attrName !== DateRangeAttribute.CUE &&
21
33
  attrName !== DateRangeAttribute.START_DATE &&
22
34
  attrName !== DateRangeAttribute.DURATION &&
23
35
  attrName !== DateRangeAttribute.END_DATE &&
@@ -28,17 +40,27 @@ export function isDateRangeCueAttribute(attrName: string): boolean {
28
40
  export function isSCTE35Attribute(attrName: string): boolean {
29
41
  return (
30
42
  attrName === DateRangeAttribute.SCTE35_OUT ||
31
- attrName === DateRangeAttribute.SCTE35_IN
43
+ attrName === DateRangeAttribute.SCTE35_IN ||
44
+ attrName === DateRangeAttribute.SCTE35_CMD
32
45
  );
33
46
  }
34
47
 
35
48
  export class DateRange {
36
49
  public attr: AttrList;
50
+ public tagAnchor: Fragment | null;
51
+ public tagOrder: number;
37
52
  private _startDate: Date;
38
53
  private _endDate?: Date;
54
+ private _cue?: DateRangeCue;
39
55
  private _badValueForSameId?: string;
40
56
 
41
- constructor(dateRangeAttr: AttrList, dateRangeWithSameId?: DateRange) {
57
+ constructor(
58
+ dateRangeAttr: AttrList,
59
+ dateRangeWithSameId?: DateRange | undefined,
60
+ tagCount: number = 0,
61
+ ) {
62
+ this.tagAnchor = dateRangeWithSameId?.tagAnchor || null;
63
+ this.tagOrder = dateRangeWithSameId?.tagOrder ?? tagCount;
42
64
  if (dateRangeWithSameId) {
43
65
  const previousAttr = dateRangeWithSameId.attr;
44
66
  for (const key in previousAttr) {
@@ -61,9 +83,13 @@ export class DateRange {
61
83
  );
62
84
  }
63
85
  this.attr = dateRangeAttr;
64
- this._startDate = new Date(dateRangeAttr[DateRangeAttribute.START_DATE]);
86
+ this._startDate = dateRangeWithSameId
87
+ ? dateRangeWithSameId.startDate
88
+ : new Date(dateRangeAttr[DateRangeAttribute.START_DATE]);
65
89
  if (DateRangeAttribute.END_DATE in this.attr) {
66
- const endDate = new Date(this.attr[DateRangeAttribute.END_DATE]);
90
+ const endDate =
91
+ dateRangeWithSameId?.endDate ||
92
+ new Date(this.attr[DateRangeAttribute.END_DATE]);
67
93
  if (Number.isFinite(endDate.getTime())) {
68
94
  this._endDate = endDate;
69
95
  }
@@ -78,6 +104,36 @@ export class DateRange {
78
104
  return this.attr.CLASS;
79
105
  }
80
106
 
107
+ get cue(): DateRangeCue {
108
+ const _cue = this._cue;
109
+ if (_cue === undefined) {
110
+ return (this._cue = this.attr.enumeratedStringList(
111
+ this.attr.CUE ? 'CUE' : 'X-CUE',
112
+ {
113
+ pre: false,
114
+ post: false,
115
+ once: false,
116
+ },
117
+ ));
118
+ }
119
+ return _cue;
120
+ }
121
+
122
+ get startTime(): number {
123
+ const { tagAnchor } = this;
124
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
125
+ if (tagAnchor === null || tagAnchor.programDateTime === null) {
126
+ logger.warn(
127
+ `Expected tagAnchor Fragment with PDT set for DateRange "${this.id}": ${tagAnchor}`,
128
+ );
129
+ return NaN;
130
+ }
131
+ return (
132
+ tagAnchor.start +
133
+ (this.startDate.getTime() - tagAnchor.programDateTime) / 1000
134
+ );
135
+ }
136
+
81
137
  get startDate(): Date {
82
138
  return this._startDate;
83
139
  }
@@ -120,13 +176,23 @@ export class DateRange {
120
176
  return this.attr.bool(DateRangeAttribute.END_ON_NEXT);
121
177
  }
122
178
 
179
+ get isInterstitial(): boolean {
180
+ return this.class === CLASS_INTERSTITIAL;
181
+ }
182
+
123
183
  get isValid(): boolean {
124
184
  return (
125
185
  !!this.id &&
126
186
  !this._badValueForSameId &&
127
187
  Number.isFinite(this.startDate.getTime()) &&
128
188
  (this.duration === null || this.duration >= 0) &&
129
- (!this.endOnNext || !!this.class)
189
+ (!this.endOnNext || !!this.class) &&
190
+ (!this.attr.CUE ||
191
+ (!this.cue.pre && !this.cue.post) ||
192
+ this.cue.pre !== this.cue.post) &&
193
+ (!this.isInterstitial ||
194
+ 'X-ASSET-URI' in this.attr ||
195
+ 'X-ASSET-LIST' in this.attr)
130
196
  );
131
197
  }
132
198
  }
@@ -1,18 +1,17 @@
1
1
  import { ErrorTypes, ErrorDetails } from '../errors';
2
- import { Fragment } from './fragment';
3
- import {
4
- Loader,
5
- LoaderConfiguration,
6
- FragmentLoaderContext,
7
- } from '../types/loader';
8
2
  import { getLoaderConfigWithoutReties } from '../utils/error-helper';
9
3
  import type { HlsConfig } from '../config';
10
- import type { BaseSegment, Part } from './fragment';
4
+ import type { BaseSegment, Fragment, Part } from './fragment';
11
5
  import type {
12
6
  ErrorData,
13
7
  FragLoadedData,
14
8
  PartsLoadedData,
15
9
  } from '../types/events';
10
+ import type {
11
+ Loader,
12
+ LoaderConfiguration,
13
+ FragmentLoaderContext,
14
+ } from '../types/loader';
16
15
 
17
16
  const MIN_CHUNK_SIZE = Math.pow(2, 17); // 128kb
18
17
 
@@ -77,13 +76,11 @@ export default class FragmentLoader {
77
76
  frag.gap = false;
78
77
  }
79
78
  }
80
- const loader =
81
- (this.loader =
82
- frag.loader =
83
- FragmentILoader
84
- ? new FragmentILoader(config)
85
- : (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
79
+ const loader = (this.loader = FragmentILoader
80
+ ? new FragmentILoader(config)
81
+ : (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
86
82
  const loaderContext = createLoaderContext(frag);
83
+ frag.loader = loader;
87
84
  const loadPolicy = getLoaderConfigWithoutReties(
88
85
  config.fragLoadPolicy.default,
89
86
  );
@@ -188,13 +185,11 @@ export default class FragmentLoader {
188
185
  reject(createGapLoadError(frag, part));
189
186
  return;
190
187
  }
191
- const loader =
192
- (this.loader =
193
- frag.loader =
194
- FragmentILoader
195
- ? new FragmentILoader(config)
196
- : (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
188
+ const loader = (this.loader = FragmentILoader
189
+ ? new FragmentILoader(config)
190
+ : (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
197
191
  const loaderContext = createLoaderContext(frag, part);
192
+ frag.loader = loader;
198
193
  // Should we define another load policy for parts?
199
194
  const loadPolicy = getLoaderConfigWithoutReties(
200
195
  config.fragLoadPolicy.default,
@@ -336,8 +331,11 @@ function createLoaderContext(
336
331
  if (Number.isFinite(start) && Number.isFinite(end)) {
337
332
  let byteRangeStart = start;
338
333
  let byteRangeEnd = end;
339
- if (frag.sn === 'initSegment' && frag.decryptdata?.method === 'AES-128') {
340
- // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
334
+ if (
335
+ frag.sn === 'initSegment' &&
336
+ isMethodFullSegmentAesCbc(frag.decryptdata?.method)
337
+ ) {
338
+ // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
341
339
  // has the unencrypted size specified in the range.
342
340
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
343
341
  const fragmentLen = end - start;
@@ -372,6 +370,10 @@ function createGapLoadError(frag: Fragment, part?: Part): LoadError {
372
370
  return new LoadError(errorData);
373
371
  }
374
372
 
373
+ function isMethodFullSegmentAesCbc(method) {
374
+ return method === 'AES-128' || method === 'AES-256';
375
+ }
376
+
375
377
  export class LoadError extends Error {
376
378
  public readonly data: FragLoadFailResult;
377
379
  constructor(data: FragLoadFailResult) {