hls.js 1.5.4 → 1.5.5-0.canary.9978

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 (67) hide show
  1. package/README.md +1 -0
  2. package/dist/hls-demo.js +10 -0
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +1935 -1094
  5. package/dist/hls.js.d.ts +63 -50
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1059 -838
  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 +846 -626
  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 +1640 -814
  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 +18 -18
  20. package/src/config.ts +3 -2
  21. package/src/controller/abr-controller.ts +21 -20
  22. package/src/controller/audio-stream-controller.ts +15 -16
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +7 -7
  25. package/src/controller/base-stream-controller.ts +56 -29
  26. package/src/controller/buffer-controller.ts +11 -11
  27. package/src/controller/cap-level-controller.ts +1 -2
  28. package/src/controller/cmcd-controller.ts +25 -3
  29. package/src/controller/content-steering-controller.ts +8 -6
  30. package/src/controller/eme-controller.ts +9 -22
  31. package/src/controller/error-controller.ts +6 -8
  32. package/src/controller/fps-controller.ts +2 -3
  33. package/src/controller/gap-controller.ts +43 -16
  34. package/src/controller/latency-controller.ts +9 -11
  35. package/src/controller/level-controller.ts +5 -17
  36. package/src/controller/stream-controller.ts +25 -32
  37. package/src/controller/subtitle-stream-controller.ts +13 -14
  38. package/src/controller/subtitle-track-controller.ts +5 -3
  39. package/src/controller/timeline-controller.ts +23 -30
  40. package/src/crypt/aes-crypto.ts +21 -2
  41. package/src/crypt/decrypter-aes-mode.ts +4 -0
  42. package/src/crypt/decrypter.ts +32 -18
  43. package/src/crypt/fast-aes-key.ts +24 -5
  44. package/src/demux/audio/adts.ts +9 -4
  45. package/src/demux/sample-aes.ts +2 -0
  46. package/src/demux/transmuxer-interface.ts +4 -12
  47. package/src/demux/transmuxer-worker.ts +4 -4
  48. package/src/demux/transmuxer.ts +16 -3
  49. package/src/demux/tsdemuxer.ts +71 -37
  50. package/src/demux/video/avc-video-parser.ts +208 -119
  51. package/src/demux/video/base-video-parser.ts +134 -2
  52. package/src/demux/video/exp-golomb.ts +0 -208
  53. package/src/demux/video/hevc-video-parser.ts +746 -0
  54. package/src/events.ts +7 -0
  55. package/src/hls.ts +42 -34
  56. package/src/loader/fragment-loader.ts +9 -2
  57. package/src/loader/key-loader.ts +2 -0
  58. package/src/loader/level-key.ts +10 -9
  59. package/src/remux/mp4-generator.ts +196 -1
  60. package/src/remux/mp4-remuxer.ts +23 -7
  61. package/src/task-loop.ts +5 -2
  62. package/src/types/component-api.ts +2 -0
  63. package/src/types/demuxer.ts +3 -0
  64. package/src/types/events.ts +4 -0
  65. package/src/utils/codecs.ts +33 -4
  66. package/src/utils/encryption-methods-util.ts +21 -0
  67. package/src/utils/logger.ts +53 -24
@@ -256,6 +256,7 @@ let Events = /*#__PURE__*/function (Events) {
256
256
  Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
257
257
  Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
258
258
  Events["MEDIA_DETACHED"] = "hlsMediaDetached";
259
+ Events["MEDIA_ENDED"] = "hlsMediaEnded";
259
260
  Events["BUFFER_RESET"] = "hlsBufferReset";
260
261
  Events["BUFFER_CODECS"] = "hlsBufferCodecs";
261
262
  Events["BUFFER_CREATED"] = "hlsBufferCreated";
@@ -369,6 +370,23 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
369
370
  return ErrorDetails;
370
371
  }({});
371
372
 
373
+ class Logger {
374
+ constructor(label, logger) {
375
+ this.trace = void 0;
376
+ this.debug = void 0;
377
+ this.log = void 0;
378
+ this.warn = void 0;
379
+ this.info = void 0;
380
+ this.error = void 0;
381
+ const lb = `[${label}]:`;
382
+ this.trace = noop;
383
+ this.debug = logger.debug.bind(null, lb);
384
+ this.log = logger.log.bind(null, lb);
385
+ this.warn = logger.warn.bind(null, lb);
386
+ this.info = logger.info.bind(null, lb);
387
+ this.error = logger.error.bind(null, lb);
388
+ }
389
+ }
372
390
  const noop = function noop() {};
373
391
  const fakeLogger = {
374
392
  trace: noop,
@@ -378,7 +396,9 @@ const fakeLogger = {
378
396
  info: noop,
379
397
  error: noop
380
398
  };
381
- let exportedLogger = fakeLogger;
399
+ function createLogger() {
400
+ return _extends({}, fakeLogger);
401
+ }
382
402
 
383
403
  // let lastCallTime;
384
404
  // function formatMsgWithTimeInfo(type, msg) {
@@ -389,35 +409,36 @@ let exportedLogger = fakeLogger;
389
409
  // return msg;
390
410
  // }
391
411
 
392
- function consolePrintFn(type) {
412
+ function consolePrintFn(type, id) {
393
413
  const func = self.console[type];
394
- if (func) {
395
- return func.bind(self.console, `[${type}] >`);
396
- }
397
- return noop;
414
+ return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
398
415
  }
399
- function exportLoggerFunctions(debugConfig, ...functions) {
400
- functions.forEach(function (type) {
401
- exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
402
- });
416
+ function getLoggerFn(key, debugConfig, id) {
417
+ return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
403
418
  }
404
- function enableLogs(debugConfig, id) {
419
+ let exportedLogger = createLogger();
420
+ function enableLogs(debugConfig, context, id) {
405
421
  // check that console is available
422
+ const newLogger = createLogger();
406
423
  if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
407
- exportLoggerFunctions(debugConfig,
424
+ const keys = [
408
425
  // Remove out from list here to hard-disable a log-level
409
426
  // 'trace',
410
- 'debug', 'log', 'info', 'warn', 'error');
427
+ 'debug', 'log', 'info', 'warn', 'error'];
428
+ keys.forEach(key => {
429
+ newLogger[key] = getLoggerFn(key, debugConfig, id);
430
+ });
411
431
  // Some browsers don't allow to use bind on console object anyway
412
432
  // fallback to default if needed
413
433
  try {
414
- exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.4"}`);
434
+ newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.5-0.canary.9978"}`);
415
435
  } catch (e) {
416
- exportedLogger = fakeLogger;
436
+ /* log fn threw an exception. All logger methods are no-ops. */
437
+ return createLogger();
417
438
  }
418
- } else {
419
- exportedLogger = fakeLogger;
420
439
  }
440
+ exportedLogger = newLogger;
441
+ return newLogger;
421
442
  }
422
443
  const logger = exportedLogger;
423
444
 
@@ -991,10 +1012,30 @@ class LevelDetails {
991
1012
  }
992
1013
  }
993
1014
 
1015
+ var DecrypterAesMode = {
1016
+ cbc: 0,
1017
+ ctr: 1
1018
+ };
1019
+
1020
+ function isFullSegmentEncryption(method) {
1021
+ return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
1022
+ }
1023
+ function getAesModeFromFullSegmentMethod(method) {
1024
+ switch (method) {
1025
+ case 'AES-128':
1026
+ case 'AES-256':
1027
+ return DecrypterAesMode.cbc;
1028
+ case 'AES-256-CTR':
1029
+ return DecrypterAesMode.ctr;
1030
+ default:
1031
+ throw new Error(`invalid full segment method ${method}`);
1032
+ }
1033
+ }
1034
+
994
1035
  // This file is inserted as a shim for modules which we do not want to include into the distro.
995
1036
  // This replacement is done in the "alias" plugin of the rollup config.
996
1037
  var empty = undefined;
997
- var Cues = /*@__PURE__*/getDefaultExportFromCjs(empty);
1038
+ var HevcVideoParser = /*@__PURE__*/getDefaultExportFromCjs(empty);
998
1039
 
999
1040
  function sliceUint8(array, start, end) {
1000
1041
  // @ts-expect-error This polyfills IE11 usage of Uint8Array slice.
@@ -2431,12 +2472,12 @@ class LevelKey {
2431
2472
  this.keyFormatVersions = formatversions;
2432
2473
  this.iv = iv;
2433
2474
  this.encrypted = method ? method !== 'NONE' : false;
2434
- this.isCommonEncryption = this.encrypted && method !== 'AES-128';
2475
+ this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
2435
2476
  }
2436
2477
  isSupported() {
2437
2478
  // If it's Segment encryption or No encryption, just select that key system
2438
2479
  if (this.method) {
2439
- if (this.method === 'AES-128' || this.method === 'NONE') {
2480
+ if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
2440
2481
  return true;
2441
2482
  }
2442
2483
  if (this.keyFormat === 'identity') {
@@ -2450,14 +2491,13 @@ class LevelKey {
2450
2491
  if (!this.encrypted || !this.uri) {
2451
2492
  return null;
2452
2493
  }
2453
- if (this.method === 'AES-128' && this.uri && !this.iv) {
2494
+ if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
2454
2495
  if (typeof sn !== 'number') {
2455
2496
  // We are fetching decryption data for a initialization segment
2456
- // If the segment was encrypted with AES-128
2497
+ // If the segment was encrypted with AES-128/256
2457
2498
  // It must have an IV defined. We cannot substitute the Segment Number in.
2458
- if (this.method === 'AES-128' && !this.iv) {
2459
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2460
- }
2499
+ logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2500
+
2461
2501
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
2462
2502
  sn = 0;
2463
2503
  }
@@ -2604,23 +2644,28 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
2604
2644
  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
2605
2645
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
2606
2646
  }
2607
-
2608
- // Idealy fLaC and Opus would be first (spec-compliant) but
2609
- // some browsers will report that fLaC is supported then fail.
2610
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
2611
2647
  const codecsToCheck = {
2648
+ // Idealy fLaC and Opus would be first (spec-compliant) but
2649
+ // some browsers will report that fLaC is supported then fail.
2650
+ // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
2612
2651
  flac: ['flac', 'fLaC', 'FLAC'],
2613
- opus: ['opus', 'Opus']
2652
+ opus: ['opus', 'Opus'],
2653
+ // Replace audio codec info if browser does not support mp4a.40.34,
2654
+ // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
2655
+ 'mp4a.40.34': ['mp3']
2614
2656
  }[lowerCaseCodec];
2615
2657
  for (let i = 0; i < codecsToCheck.length; i++) {
2658
+ var _getMediaSource;
2616
2659
  if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
2617
2660
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
2618
2661
  return codecsToCheck[i];
2662
+ } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
2663
+ return '';
2619
2664
  }
2620
2665
  }
2621
2666
  return lowerCaseCodec;
2622
2667
  }
2623
- const AUDIO_CODEC_REGEXP = /flac|opus/i;
2668
+ const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
2624
2669
  function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
2625
2670
  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
2626
2671
  }
@@ -2643,6 +2688,16 @@ function convertAVC1ToAVCOTI(codec) {
2643
2688
  }
2644
2689
  return codec;
2645
2690
  }
2691
+ function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
2692
+ const MediaSource = getMediaSource(preferManagedMediaSource) || {
2693
+ isTypeSupported: () => false
2694
+ };
2695
+ return {
2696
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
2697
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
2698
+ ac3: false
2699
+ };
2700
+ }
2646
2701
 
2647
2702
  const MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g;
2648
2703
  const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -4220,7 +4275,47 @@ class LatencyController {
4220
4275
  this.currentTime = 0;
4221
4276
  this.stallCount = 0;
4222
4277
  this._latency = null;
4223
- this.timeupdateHandler = () => this.timeupdate();
4278
+ this.onTimeupdate = () => {
4279
+ const {
4280
+ media,
4281
+ levelDetails
4282
+ } = this;
4283
+ if (!media || !levelDetails) {
4284
+ return;
4285
+ }
4286
+ this.currentTime = media.currentTime;
4287
+ const latency = this.computeLatency();
4288
+ if (latency === null) {
4289
+ return;
4290
+ }
4291
+ this._latency = latency;
4292
+
4293
+ // Adapt playbackRate to meet target latency in low-latency mode
4294
+ const {
4295
+ lowLatencyMode,
4296
+ maxLiveSyncPlaybackRate
4297
+ } = this.config;
4298
+ if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4299
+ return;
4300
+ }
4301
+ const targetLatency = this.targetLatency;
4302
+ if (targetLatency === null) {
4303
+ return;
4304
+ }
4305
+ const distanceFromTarget = latency - targetLatency;
4306
+ // Only adjust playbackRate when within one target duration of targetLatency
4307
+ // and more than one second from under-buffering.
4308
+ // Playback further than one target duration from target can be considered DVR playback.
4309
+ const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4310
+ const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4311
+ if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4312
+ const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4313
+ const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4314
+ media.playbackRate = Math.min(max, Math.max(1, rate));
4315
+ } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4316
+ media.playbackRate = 1;
4317
+ }
4318
+ };
4224
4319
  this.hls = hls;
4225
4320
  this.config = hls.config;
4226
4321
  this.registerListeners();
@@ -4312,7 +4407,7 @@ class LatencyController {
4312
4407
  this.onMediaDetaching();
4313
4408
  this.levelDetails = null;
4314
4409
  // @ts-ignore
4315
- this.hls = this.timeupdateHandler = null;
4410
+ this.hls = null;
4316
4411
  }
4317
4412
  registerListeners() {
4318
4413
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -4330,11 +4425,11 @@ class LatencyController {
4330
4425
  }
4331
4426
  onMediaAttached(event, data) {
4332
4427
  this.media = data.media;
4333
- this.media.addEventListener('timeupdate', this.timeupdateHandler);
4428
+ this.media.addEventListener('timeupdate', this.onTimeupdate);
4334
4429
  }
4335
4430
  onMediaDetaching() {
4336
4431
  if (this.media) {
4337
- this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4432
+ this.media.removeEventListener('timeupdate', this.onTimeupdate);
4338
4433
  this.media = null;
4339
4434
  }
4340
4435
  }
@@ -4348,10 +4443,10 @@ class LatencyController {
4348
4443
  }) {
4349
4444
  this.levelDetails = details;
4350
4445
  if (details.advanced) {
4351
- this.timeupdate();
4446
+ this.onTimeupdate();
4352
4447
  }
4353
4448
  if (!details.live && this.media) {
4354
- this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4449
+ this.media.removeEventListener('timeupdate', this.onTimeupdate);
4355
4450
  }
4356
4451
  }
4357
4452
  onError(event, data) {
@@ -4361,48 +4456,7 @@ class LatencyController {
4361
4456
  }
4362
4457
  this.stallCount++;
4363
4458
  if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
4364
- logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
4365
- }
4366
- }
4367
- timeupdate() {
4368
- const {
4369
- media,
4370
- levelDetails
4371
- } = this;
4372
- if (!media || !levelDetails) {
4373
- return;
4374
- }
4375
- this.currentTime = media.currentTime;
4376
- const latency = this.computeLatency();
4377
- if (latency === null) {
4378
- return;
4379
- }
4380
- this._latency = latency;
4381
-
4382
- // Adapt playbackRate to meet target latency in low-latency mode
4383
- const {
4384
- lowLatencyMode,
4385
- maxLiveSyncPlaybackRate
4386
- } = this.config;
4387
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4388
- return;
4389
- }
4390
- const targetLatency = this.targetLatency;
4391
- if (targetLatency === null) {
4392
- return;
4393
- }
4394
- const distanceFromTarget = latency - targetLatency;
4395
- // Only adjust playbackRate when within one target duration of targetLatency
4396
- // and more than one second from under-buffering.
4397
- // Playback further than one target duration from target can be considered DVR playback.
4398
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4399
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4400
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4401
- const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4402
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4403
- media.playbackRate = Math.min(max, Math.max(1, rate));
4404
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4405
- media.playbackRate = 1;
4459
+ this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
4406
4460
  }
4407
4461
  }
4408
4462
  estimateLiveEdge() {
@@ -5174,18 +5228,13 @@ var ErrorActionFlags = {
5174
5228
  MoveAllAlternatesMatchingHDCP: 2,
5175
5229
  SwitchToSDR: 4
5176
5230
  }; // Reserved for future use
5177
- class ErrorController {
5231
+ class ErrorController extends Logger {
5178
5232
  constructor(hls) {
5233
+ super('error-controller', hls.logger);
5179
5234
  this.hls = void 0;
5180
5235
  this.playlistError = 0;
5181
5236
  this.penalizedRenditions = {};
5182
- this.log = void 0;
5183
- this.warn = void 0;
5184
- this.error = void 0;
5185
5237
  this.hls = hls;
5186
- this.log = logger.log.bind(logger, `[info]:`);
5187
- this.warn = logger.warn.bind(logger, `[warning]:`);
5188
- this.error = logger.error.bind(logger, `[error]:`);
5189
5238
  this.registerListeners();
5190
5239
  }
5191
5240
  registerListeners() {
@@ -5537,16 +5586,13 @@ class ErrorController {
5537
5586
  }
5538
5587
  }
5539
5588
 
5540
- class BasePlaylistController {
5589
+ class BasePlaylistController extends Logger {
5541
5590
  constructor(hls, logPrefix) {
5591
+ super(logPrefix, hls.logger);
5542
5592
  this.hls = void 0;
5543
5593
  this.timer = -1;
5544
5594
  this.requestScheduled = -1;
5545
5595
  this.canLoad = false;
5546
- this.log = void 0;
5547
- this.warn = void 0;
5548
- this.log = logger.log.bind(logger, `${logPrefix}:`);
5549
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
5550
5596
  this.hls = hls;
5551
5597
  }
5552
5598
  destroy() {
@@ -5579,7 +5625,7 @@ class BasePlaylistController {
5579
5625
  try {
5580
5626
  uri = new self.URL(attr.URI, previous.url).href;
5581
5627
  } catch (error) {
5582
- logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
5628
+ this.warn(`Could not construct new URL for Rendition Report: ${error}`);
5583
5629
  uri = attr.URI || '';
5584
5630
  }
5585
5631
  // Use exact match. Otherwise, the last partial match, if any, will be used
@@ -6125,8 +6171,9 @@ function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
6125
6171
  }, {});
6126
6172
  }
6127
6173
 
6128
- class AbrController {
6174
+ class AbrController extends Logger {
6129
6175
  constructor(_hls) {
6176
+ super('abr', _hls.logger);
6130
6177
  this.hls = void 0;
6131
6178
  this.lastLevelLoadSec = 0;
6132
6179
  this.lastLoadedFragLevel = -1;
@@ -6240,7 +6287,7 @@ class AbrController {
6240
6287
  this.resetEstimator(nextLoadLevelBitrate);
6241
6288
  }
6242
6289
  this.clearTimer();
6243
- logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6290
+ this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6244
6291
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
6245
6292
  Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
6246
6293
  Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
@@ -6260,7 +6307,7 @@ class AbrController {
6260
6307
  }
6261
6308
  resetEstimator(abrEwmaDefaultEstimate) {
6262
6309
  if (abrEwmaDefaultEstimate) {
6263
- logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6310
+ this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6264
6311
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
6265
6312
  }
6266
6313
  this.firstSelection = -1;
@@ -6492,7 +6539,7 @@ class AbrController {
6492
6539
  }
6493
6540
  const firstLevel = this.hls.firstLevel;
6494
6541
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
6495
- logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
6542
+ this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
6496
6543
  return clamped;
6497
6544
  }
6498
6545
  get forcedAutoLevel() {
@@ -6577,13 +6624,13 @@ class AbrController {
6577
6624
  // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
6578
6625
  const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
6579
6626
  maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
6580
- logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
6627
+ this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
6581
6628
  // don't use conservative factor on bitrate test
6582
6629
  bwFactor = bwUpFactor = 1;
6583
6630
  }
6584
6631
  }
6585
6632
  const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
6586
- logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
6633
+ this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
6587
6634
  if (bestLevel > -1) {
6588
6635
  return bestLevel;
6589
6636
  }
@@ -6645,7 +6692,7 @@ class AbrController {
6645
6692
  currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
6646
6693
  currentFrameRate = minFramerate;
6647
6694
  currentBw = Math.max(currentBw, minBitrate);
6648
- logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
6695
+ this.log(`picked start tier ${JSON.stringify(startTier)}`);
6649
6696
  } else {
6650
6697
  currentCodecSet = level == null ? void 0 : level.codecSet;
6651
6698
  currentVideoRange = level == null ? void 0 : level.videoRange;
@@ -6698,9 +6745,9 @@ class AbrController {
6698
6745
  const forcedAutoLevel = this.forcedAutoLevel;
6699
6746
  if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
6700
6747
  if (levelsSkipped.length) {
6701
- logger.trace(`[abr] Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
6748
+ this.trace(`Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
6702
6749
  }
6703
- logger.info(`[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
6750
+ this.info(`switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
6704
6751
  }
6705
6752
  if (firstSelection) {
6706
6753
  this.firstSelection = i;
@@ -6936,8 +6983,9 @@ class BufferOperationQueue {
6936
6983
  }
6937
6984
 
6938
6985
  const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
6939
- class BufferController {
6986
+ class BufferController extends Logger {
6940
6987
  constructor(hls) {
6988
+ super('buffer-controller', hls.logger);
6941
6989
  // The level details used to determine duration, target-duration and live
6942
6990
  this.details = null;
6943
6991
  // cache the self generated object url to detect hijack of video tag
@@ -6967,9 +7015,6 @@ class BufferController {
6967
7015
  this.tracks = {};
6968
7016
  this.pendingTracks = {};
6969
7017
  this.sourceBuffer = void 0;
6970
- this.log = void 0;
6971
- this.warn = void 0;
6972
- this.error = void 0;
6973
7018
  this._onEndStreaming = event => {
6974
7019
  if (!this.hls) {
6975
7020
  return;
@@ -7015,15 +7060,11 @@ class BufferController {
7015
7060
  _objectUrl
7016
7061
  } = this;
7017
7062
  if (mediaSrc !== _objectUrl) {
7018
- logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
7063
+ this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
7019
7064
  }
7020
7065
  };
7021
7066
  this.hls = hls;
7022
- const logPrefix = '[buffer-controller]';
7023
7067
  this.appendSource = hls.config.preferManagedMediaSource;
7024
- this.log = logger.log.bind(logger, logPrefix);
7025
- this.warn = logger.warn.bind(logger, logPrefix);
7026
- this.error = logger.error.bind(logger, logPrefix);
7027
7068
  this._initSourceBuffer();
7028
7069
  this.registerListeners();
7029
7070
  }
@@ -7036,6 +7077,12 @@ class BufferController {
7036
7077
  this.lastMpegAudioChunk = null;
7037
7078
  // @ts-ignore
7038
7079
  this.hls = null;
7080
+ // @ts-ignore
7081
+ this._onMediaSourceOpen = this._onMediaSourceClose = null;
7082
+ // @ts-ignore
7083
+ this._onMediaSourceEnded = null;
7084
+ // @ts-ignore
7085
+ this._onStartStreaming = this._onEndStreaming = null;
7039
7086
  }
7040
7087
  registerListeners() {
7041
7088
  const {
@@ -7198,6 +7245,7 @@ class BufferController {
7198
7245
  this.resetBuffer(type);
7199
7246
  });
7200
7247
  this._initSourceBuffer();
7248
+ this.hls.resumeBuffering();
7201
7249
  }
7202
7250
  resetBuffer(type) {
7203
7251
  const sb = this.sourceBuffer[type];
@@ -8035,7 +8083,7 @@ class CapLevelController {
8035
8083
  const hls = this.hls;
8036
8084
  const maxLevel = this.getMaxLevel(levels.length - 1);
8037
8085
  if (maxLevel !== this.autoLevelCapping) {
8038
- logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
8086
+ hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
8039
8087
  }
8040
8088
  hls.autoLevelCapping = maxLevel;
8041
8089
  if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
@@ -8213,10 +8261,10 @@ class FPSController {
8213
8261
  totalDroppedFrames: droppedFrames
8214
8262
  });
8215
8263
  if (droppedFPS > 0) {
8216
- // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
8264
+ // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
8217
8265
  if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
8218
8266
  let currentLevel = hls.currentLevel;
8219
- logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
8267
+ hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
8220
8268
  if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
8221
8269
  currentLevel = currentLevel - 1;
8222
8270
  hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
@@ -8249,10 +8297,10 @@ class FPSController {
8249
8297
  }
8250
8298
 
8251
8299
  const PATHWAY_PENALTY_DURATION_MS = 300000;
8252
- class ContentSteeringController {
8300
+ class ContentSteeringController extends Logger {
8253
8301
  constructor(hls) {
8302
+ super('content-steering', hls.logger);
8254
8303
  this.hls = void 0;
8255
- this.log = void 0;
8256
8304
  this.loader = null;
8257
8305
  this.uri = null;
8258
8306
  this.pathwayId = '.';
@@ -8267,7 +8315,6 @@ class ContentSteeringController {
8267
8315
  this.subtitleTracks = null;
8268
8316
  this.penalizedPathways = {};
8269
8317
  this.hls = hls;
8270
- this.log = logger.log.bind(logger, `[content-steering]:`);
8271
8318
  this.registerListeners();
8272
8319
  }
8273
8320
  registerListeners() {
@@ -8391,7 +8438,7 @@ class ContentSteeringController {
8391
8438
  errorAction.resolved = this.pathwayId !== errorPathway;
8392
8439
  }
8393
8440
  if (!errorAction.resolved) {
8394
- logger.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
8441
+ this.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
8395
8442
  }
8396
8443
  }
8397
8444
  }
@@ -8562,7 +8609,7 @@ class ContentSteeringController {
8562
8609
  onSuccess: (response, stats, context, networkDetails) => {
8563
8610
  this.log(`Loaded steering manifest: "${url}"`);
8564
8611
  const steeringData = response.data;
8565
- if (steeringData.VERSION !== 1) {
8612
+ if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
8566
8613
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
8567
8614
  return;
8568
8615
  }
@@ -9470,7 +9517,7 @@ const hlsDefaultConfig = _objectSpread2(_objectSpread2({
9470
9517
  });
9471
9518
  function timelineConfig() {
9472
9519
  return {
9473
- cueHandler: Cues,
9520
+ cueHandler: HevcVideoParser,
9474
9521
  // used by timeline-controller
9475
9522
  enableWebVTT: false,
9476
9523
  // used by timeline-controller
@@ -9501,7 +9548,7 @@ function timelineConfig() {
9501
9548
  /**
9502
9549
  * @ignore
9503
9550
  */
9504
- function mergeConfig(defaultConfig, userConfig) {
9551
+ function mergeConfig(defaultConfig, userConfig, logger) {
9505
9552
  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
9506
9553
  throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
9507
9554
  }
@@ -9571,7 +9618,7 @@ function deepCpy(obj) {
9571
9618
  /**
9572
9619
  * @ignore
9573
9620
  */
9574
- function enableStreamingMode(config) {
9621
+ function enableStreamingMode(config, logger) {
9575
9622
  const currentLoader = config.loader;
9576
9623
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
9577
9624
  // If a developer has configured their own loader, respect that choice
@@ -9588,10 +9635,9 @@ function enableStreamingMode(config) {
9588
9635
  }
9589
9636
  }
9590
9637
 
9591
- let chromeOrFirefox;
9592
9638
  class LevelController extends BasePlaylistController {
9593
9639
  constructor(hls, contentSteeringController) {
9594
- super(hls, '[level-controller]');
9640
+ super(hls, 'level-controller');
9595
9641
  this._levels = [];
9596
9642
  this._firstLevel = -1;
9597
9643
  this._maxAutoLevel = -1;
@@ -9662,23 +9708,15 @@ class LevelController extends BasePlaylistController {
9662
9708
  let videoCodecFound = false;
9663
9709
  let audioCodecFound = false;
9664
9710
  data.levels.forEach(levelParsed => {
9665
- var _audioCodec, _videoCodec;
9711
+ var _videoCodec;
9666
9712
  const attributes = levelParsed.attrs;
9667
-
9668
- // erase audio codec info if browser does not support mp4a.40.34.
9669
- // demuxer will autodetect codec and fallback to mpeg/audio
9670
9713
  let {
9671
9714
  audioCodec,
9672
9715
  videoCodec
9673
9716
  } = levelParsed;
9674
- if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
9675
- chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
9676
- if (chromeOrFirefox) {
9677
- levelParsed.audioCodec = audioCodec = undefined;
9678
- }
9679
- }
9680
9717
  if (audioCodec) {
9681
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
9718
+ // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
9719
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
9682
9720
  }
9683
9721
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
9684
9722
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -10806,8 +10844,8 @@ function createLoaderContext(frag, part = null) {
10806
10844
  var _frag$decryptdata;
10807
10845
  let byteRangeStart = start;
10808
10846
  let byteRangeEnd = end;
10809
- if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
10810
- // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
10847
+ if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
10848
+ // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
10811
10849
  // has the unencrypted size specified in the range.
10812
10850
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
10813
10851
  const fragmentLen = end - start;
@@ -10840,6 +10878,9 @@ function createGapLoadError(frag, part) {
10840
10878
  (part ? part : frag).stats.aborted = true;
10841
10879
  return new LoadError(errorData);
10842
10880
  }
10881
+ function isMethodFullSegmentAesCbc(method) {
10882
+ return method === 'AES-128' || method === 'AES-256';
10883
+ }
10843
10884
  class LoadError extends Error {
10844
10885
  constructor(data) {
10845
10886
  super(data.error.message);
@@ -10985,6 +11026,8 @@ class KeyLoader {
10985
11026
  }
10986
11027
  return this.loadKeyEME(keyInfo, frag);
10987
11028
  case 'AES-128':
11029
+ case 'AES-256':
11030
+ case 'AES-256-CTR':
10988
11031
  return this.loadKeyHTTP(keyInfo, frag);
10989
11032
  default:
10990
11033
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -11120,8 +11163,9 @@ class KeyLoader {
11120
11163
  * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
11121
11164
  * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
11122
11165
  */
11123
- class TaskLoop {
11124
- constructor() {
11166
+ class TaskLoop extends Logger {
11167
+ constructor(label, logger) {
11168
+ super(label, logger);
11125
11169
  this._boundTick = void 0;
11126
11170
  this._tickTimer = null;
11127
11171
  this._tickInterval = null;
@@ -11389,33 +11433,61 @@ function alignMediaPlaylistByPDT(details, refDetails) {
11389
11433
  }
11390
11434
 
11391
11435
  class AESCrypto {
11392
- constructor(subtle, iv) {
11436
+ constructor(subtle, iv, aesMode) {
11393
11437
  this.subtle = void 0;
11394
11438
  this.aesIV = void 0;
11439
+ this.aesMode = void 0;
11395
11440
  this.subtle = subtle;
11396
11441
  this.aesIV = iv;
11442
+ this.aesMode = aesMode;
11397
11443
  }
11398
11444
  decrypt(data, key) {
11399
- return this.subtle.decrypt({
11400
- name: 'AES-CBC',
11401
- iv: this.aesIV
11402
- }, key, data);
11445
+ switch (this.aesMode) {
11446
+ case DecrypterAesMode.cbc:
11447
+ return this.subtle.decrypt({
11448
+ name: 'AES-CBC',
11449
+ iv: this.aesIV
11450
+ }, key, data);
11451
+ case DecrypterAesMode.ctr:
11452
+ return this.subtle.decrypt({
11453
+ name: 'AES-CTR',
11454
+ counter: this.aesIV,
11455
+ length: 64
11456
+ },
11457
+ //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
11458
+ key, data);
11459
+ default:
11460
+ throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
11461
+ }
11403
11462
  }
11404
11463
  }
11405
11464
 
11406
11465
  class FastAESKey {
11407
- constructor(subtle, key) {
11466
+ constructor(subtle, key, aesMode) {
11408
11467
  this.subtle = void 0;
11409
11468
  this.key = void 0;
11469
+ this.aesMode = void 0;
11410
11470
  this.subtle = subtle;
11411
11471
  this.key = key;
11472
+ this.aesMode = aesMode;
11412
11473
  }
11413
11474
  expandKey() {
11475
+ const subtleAlgoName = getSubtleAlgoName(this.aesMode);
11414
11476
  return this.subtle.importKey('raw', this.key, {
11415
- name: 'AES-CBC'
11477
+ name: subtleAlgoName
11416
11478
  }, false, ['encrypt', 'decrypt']);
11417
11479
  }
11418
11480
  }
11481
+ function getSubtleAlgoName(aesMode) {
11482
+ switch (aesMode) {
11483
+ case DecrypterAesMode.cbc:
11484
+ return 'AES-CBC';
11485
+ case DecrypterAesMode.ctr:
11486
+ return 'AES-CTR';
11487
+ default:
11488
+ throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
11489
+ }
11490
+ }
11419
11491
 
11420
11492
  // PKCS7
11421
11493
  function removePadding(array) {
@@ -11665,7 +11737,8 @@ class Decrypter {
11665
11737
  this.currentIV = null;
11666
11738
  this.currentResult = null;
11667
11739
  this.useSoftware = void 0;
11668
- this.useSoftware = config.enableSoftwareAES;
11740
+ this.enableSoftwareAES = void 0;
11741
+ this.enableSoftwareAES = config.enableSoftwareAES;
11669
11742
  this.removePKCS7Padding = removePKCS7Padding;
11670
11743
  // built in decryptor expects PKCS7 padding
11671
11744
  if (removePKCS7Padding) {
@@ -11678,9 +11751,7 @@ class Decrypter {
11678
11751
  /* no-op */
11679
11752
  }
11680
11753
  }
11681
- if (this.subtle === null) {
11682
- this.useSoftware = true;
11683
- }
11754
+ this.useSoftware = this.subtle === null;
11684
11755
  }
11685
11756
  destroy() {
11686
11757
  this.subtle = null;
@@ -11718,10 +11789,10 @@ class Decrypter {
11718
11789
  this.softwareDecrypter = null;
11719
11790
  }
11720
11791
  }
11721
- decrypt(data, key, iv) {
11792
+ decrypt(data, key, iv, aesMode) {
11722
11793
  if (this.useSoftware) {
11723
11794
  return new Promise((resolve, reject) => {
11724
- this.softwareDecrypt(new Uint8Array(data), key, iv);
11795
+ this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
11725
11796
  const decryptResult = this.flush();
11726
11797
  if (decryptResult) {
11727
11798
  resolve(decryptResult.buffer);
@@ -11730,17 +11801,21 @@ class Decrypter {
11730
11801
  }
11731
11802
  });
11732
11803
  }
11733
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
11804
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
11734
11805
  }
11735
11806
 
11736
11807
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
11737
11808
  // data is handled in the flush() call
11738
- softwareDecrypt(data, key, iv) {
11809
+ softwareDecrypt(data, key, iv, aesMode) {
11739
11810
  const {
11740
11811
  currentIV,
11741
11812
  currentResult,
11742
11813
  remainderData
11743
11814
  } = this;
11815
+ if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
11816
+ logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
11817
+ return null;
11818
+ }
11744
11819
  this.logOnce('JS AES decrypt');
11745
11820
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
11746
11821
  // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
@@ -11773,11 +11848,11 @@ class Decrypter {
11773
11848
  }
11774
11849
  return result;
11775
11850
  }
11776
- webCryptoDecrypt(data, key, iv) {
11851
+ webCryptoDecrypt(data, key, iv, aesMode) {
11777
11852
  const subtle = this.subtle;
11778
11853
  if (this.key !== key || !this.fastAesKey) {
11779
11854
  this.key = key;
11780
- this.fastAesKey = new FastAESKey(subtle, key);
11855
+ this.fastAesKey = new FastAESKey(subtle, key, aesMode);
11781
11856
  }
11782
11857
  return this.fastAesKey.expandKey().then(aesKey => {
11783
11858
  // decrypt using web crypto
@@ -11785,22 +11860,25 @@ class Decrypter {
11785
11860
  return Promise.reject(new Error('web crypto not initialized'));
11786
11861
  }
11787
11862
  this.logOnce('WebCrypto AES decrypt');
11788
- const crypto = new AESCrypto(subtle, new Uint8Array(iv));
11863
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
11789
11864
  return crypto.decrypt(data.buffer, aesKey);
11790
11865
  }).catch(err => {
11791
11866
  logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
11792
- return this.onWebCryptoError(data, key, iv);
11867
+ return this.onWebCryptoError(data, key, iv, aesMode);
11793
11868
  });
11794
11869
  }
11795
- onWebCryptoError(data, key, iv) {
11796
- this.useSoftware = true;
11797
- this.logEnabled = true;
11798
- this.softwareDecrypt(data, key, iv);
11799
- const decryptResult = this.flush();
11800
- if (decryptResult) {
11801
- return decryptResult.buffer;
11870
+ onWebCryptoError(data, key, iv, aesMode) {
11871
+ const enableSoftwareAES = this.enableSoftwareAES;
11872
+ if (enableSoftwareAES) {
11873
+ this.useSoftware = true;
11874
+ this.logEnabled = true;
11875
+ this.softwareDecrypt(data, key, iv, aesMode);
11876
+ const decryptResult = this.flush();
11877
+ if (decryptResult) {
11878
+ return decryptResult.buffer;
11879
+ }
11802
11880
  }
11803
- throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
11881
+ throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
11804
11882
  }
11805
11883
  getValidChunk(data) {
11806
11884
  let currentChunk = data;
@@ -11851,7 +11929,7 @@ const State = {
11851
11929
  };
11852
11930
  class BaseStreamController extends TaskLoop {
11853
11931
  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
11854
- super();
11932
+ super(logPrefix, hls.logger);
11855
11933
  this.hls = void 0;
11856
11934
  this.fragPrevious = null;
11857
11935
  this.fragCurrent = null;
@@ -11876,22 +11954,89 @@ class BaseStreamController extends TaskLoop {
11876
11954
  this.startFragRequested = false;
11877
11955
  this.decrypter = void 0;
11878
11956
  this.initPTS = [];
11879
- this.onvseeking = null;
11880
- this.onvended = null;
11881
- this.logPrefix = '';
11882
- this.log = void 0;
11883
- this.warn = void 0;
11957
+ this.buffering = true;
11958
+ this.onMediaSeeking = () => {
11959
+ const {
11960
+ config,
11961
+ fragCurrent,
11962
+ media,
11963
+ mediaBuffer,
11964
+ state
11965
+ } = this;
11966
+ const currentTime = media ? media.currentTime : 0;
11967
+ const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
11968
+ this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
11969
+ if (this.state === State.ENDED) {
11970
+ this.resetLoadingState();
11971
+ } else if (fragCurrent) {
11972
+ // Seeking while frag load is in progress
11973
+ const tolerance = config.maxFragLookUpTolerance;
11974
+ const fragStartOffset = fragCurrent.start - tolerance;
11975
+ const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
11976
+ // if seeking out of buffered range or into new one
11977
+ if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
11978
+ const pastFragment = currentTime > fragEndOffset;
11979
+ // if the seek position is outside the current fragment range
11980
+ if (currentTime < fragStartOffset || pastFragment) {
11981
+ if (pastFragment && fragCurrent.loader) {
11982
+ this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
11983
+ fragCurrent.abortRequests();
11984
+ this.resetLoadingState();
11985
+ }
11986
+ this.fragPrevious = null;
11987
+ }
11988
+ }
11989
+ }
11990
+ if (media) {
11991
+ // Remove gap fragments
11992
+ this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
11993
+ this.lastCurrentTime = currentTime;
11994
+ }
11995
+
11996
+ // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
11997
+ if (!this.loadedmetadata && !bufferInfo.len) {
11998
+ this.nextLoadPosition = this.startPosition = currentTime;
11999
+ }
12000
+
12001
+ // Async tick to speed up processing
12002
+ this.tickImmediate();
12003
+ };
12004
+ this.onMediaEnded = () => {
12005
+ // reset startPosition and lastCurrentTime to restart playback @ stream beginning
12006
+ this.startPosition = this.lastCurrentTime = 0;
12007
+ if (this.playlistType === PlaylistLevelType.MAIN) {
12008
+ this.hls.trigger(Events.MEDIA_ENDED, {
12009
+ stalled: false
12010
+ });
12011
+ }
12012
+ };
11884
12013
  this.playlistType = playlistType;
11885
- this.logPrefix = logPrefix;
11886
- this.log = logger.log.bind(logger, `${logPrefix}:`);
11887
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
11888
12014
  this.hls = hls;
11889
12015
  this.fragmentLoader = new FragmentLoader(hls.config);
11890
12016
  this.keyLoader = keyLoader;
11891
12017
  this.fragmentTracker = fragmentTracker;
11892
12018
  this.config = hls.config;
11893
12019
  this.decrypter = new Decrypter(hls.config);
12020
+ }
12021
+ registerListeners() {
12022
+ const {
12023
+ hls
12024
+ } = this;
12025
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
12026
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
12027
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
11894
12028
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
12029
+ hls.on(Events.ERROR, this.onError, this);
12030
+ }
12031
+ unregisterListeners() {
12032
+ const {
12033
+ hls
12034
+ } = this;
12035
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
12036
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
12037
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
12038
+ hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
12039
+ hls.off(Events.ERROR, this.onError, this);
11895
12040
  }
11896
12041
  doTick() {
11897
12042
  this.onTickEnd();
@@ -11915,6 +12060,12 @@ class BaseStreamController extends TaskLoop {
11915
12060
  this.clearNextTick();
11916
12061
  this.state = State.STOPPED;
11917
12062
  }
12063
+ pauseBuffering() {
12064
+ this.buffering = false;
12065
+ }
12066
+ resumeBuffering() {
12067
+ this.buffering = true;
12068
+ }
11918
12069
  _streamEnded(bufferInfo, levelDetails) {
11919
12070
  // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
11920
12071
  // of nothing loading/loaded return false
@@ -11945,10 +12096,8 @@ class BaseStreamController extends TaskLoop {
11945
12096
  }
11946
12097
  onMediaAttached(event, data) {
11947
12098
  const media = this.media = this.mediaBuffer = data.media;
11948
- this.onvseeking = this.onMediaSeeking.bind(this);
11949
- this.onvended = this.onMediaEnded.bind(this);
11950
- media.addEventListener('seeking', this.onvseeking);
11951
- media.addEventListener('ended', this.onvended);
12099
+ media.addEventListener('seeking', this.onMediaSeeking);
12100
+ media.addEventListener('ended', this.onMediaEnded);
11952
12101
  const config = this.config;
11953
12102
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
11954
12103
  this.startLoad(config.startPosition);
@@ -11962,10 +12111,9 @@ class BaseStreamController extends TaskLoop {
11962
12111
  }
11963
12112
 
11964
12113
  // remove video listeners
11965
- if (media && this.onvseeking && this.onvended) {
11966
- media.removeEventListener('seeking', this.onvseeking);
11967
- media.removeEventListener('ended', this.onvended);
11968
- this.onvseeking = this.onvended = null;
12114
+ if (media) {
12115
+ media.removeEventListener('seeking', this.onMediaSeeking);
12116
+ media.removeEventListener('ended', this.onMediaEnded);
11969
12117
  }
11970
12118
  if (this.keyLoader) {
11971
12119
  this.keyLoader.detach();
@@ -11975,56 +12123,8 @@ class BaseStreamController extends TaskLoop {
11975
12123
  this.fragmentTracker.removeAllFragments();
11976
12124
  this.stopLoad();
11977
12125
  }
11978
- onMediaSeeking() {
11979
- const {
11980
- config,
11981
- fragCurrent,
11982
- media,
11983
- mediaBuffer,
11984
- state
11985
- } = this;
11986
- const currentTime = media ? media.currentTime : 0;
11987
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
11988
- this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
11989
- if (this.state === State.ENDED) {
11990
- this.resetLoadingState();
11991
- } else if (fragCurrent) {
11992
- // Seeking while frag load is in progress
11993
- const tolerance = config.maxFragLookUpTolerance;
11994
- const fragStartOffset = fragCurrent.start - tolerance;
11995
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
11996
- // if seeking out of buffered range or into new one
11997
- if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
11998
- const pastFragment = currentTime > fragEndOffset;
11999
- // if the seek position is outside the current fragment range
12000
- if (currentTime < fragStartOffset || pastFragment) {
12001
- if (pastFragment && fragCurrent.loader) {
12002
- this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
12003
- fragCurrent.abortRequests();
12004
- this.resetLoadingState();
12005
- }
12006
- this.fragPrevious = null;
12007
- }
12008
- }
12009
- }
12010
- if (media) {
12011
- // Remove gap fragments
12012
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
12013
- this.lastCurrentTime = currentTime;
12014
- }
12015
-
12016
- // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
12017
- if (!this.loadedmetadata && !bufferInfo.len) {
12018
- this.nextLoadPosition = this.startPosition = currentTime;
12019
- }
12020
-
12021
- // Async tick to speed up processing
12022
- this.tickImmediate();
12023
- }
12024
- onMediaEnded() {
12025
- // reset startPosition and lastCurrentTime to restart playback @ stream beginning
12026
- this.startPosition = this.lastCurrentTime = 0;
12027
- }
12126
+ onManifestLoading() {}
12127
+ onError(event, data) {}
12028
12128
  onManifestLoaded(event, data) {
12029
12129
  this.startTimeOffset = data.startTimeOffset;
12030
12130
  this.initPTS = [];
@@ -12034,7 +12134,7 @@ class BaseStreamController extends TaskLoop {
12034
12134
  this.stopLoad();
12035
12135
  super.onHandlerDestroying();
12036
12136
  // @ts-ignore
12037
- this.hls = null;
12137
+ this.hls = this.onMediaSeeking = this.onMediaEnded = null;
12038
12138
  }
12039
12139
  onHandlerDestroyed() {
12040
12140
  this.state = State.STOPPED;
@@ -12165,10 +12265,10 @@ class BaseStreamController extends TaskLoop {
12165
12265
  const decryptData = frag.decryptdata;
12166
12266
 
12167
12267
  // check to see if the payload needs to be decrypted
12168
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
12268
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
12169
12269
  const startTime = self.performance.now();
12170
12270
  // decrypt init segment data
12171
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
12271
+ return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
12172
12272
  hls.trigger(Events.ERROR, {
12173
12273
  type: ErrorTypes.MEDIA_ERROR,
12174
12274
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -12280,7 +12380,7 @@ class BaseStreamController extends TaskLoop {
12280
12380
  }
12281
12381
  let keyLoadingPromise = null;
12282
12382
  if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
12283
- this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
12383
+ this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
12284
12384
  this.state = State.KEY_LOADING;
12285
12385
  this.fragCurrent = frag;
12286
12386
  keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
@@ -12311,7 +12411,7 @@ class BaseStreamController extends TaskLoop {
12311
12411
  const partIndex = this.getNextPart(partList, frag, targetBufferTime);
12312
12412
  if (partIndex > -1) {
12313
12413
  const part = partList[partIndex];
12314
- this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
12414
+ this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
12315
12415
  this.nextLoadPosition = part.start + part.duration;
12316
12416
  this.state = State.FRAG_LOADING;
12317
12417
  let _result;
@@ -12340,7 +12440,7 @@ class BaseStreamController extends TaskLoop {
12340
12440
  }
12341
12441
  }
12342
12442
  }
12343
- this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
12443
+ this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
12344
12444
  // Don't update nextLoadPosition for fragments which are not buffered
12345
12445
  if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
12346
12446
  this.nextLoadPosition = frag.start + frag.duration;
@@ -12925,7 +13025,7 @@ class BaseStreamController extends TaskLoop {
12925
13025
  errorAction.resolved = true;
12926
13026
  }
12927
13027
  } else {
12928
- logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
13028
+ this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
12929
13029
  return;
12930
13030
  }
12931
13031
  } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
@@ -13320,6 +13420,7 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
13320
13420
  */
13321
13421
  function getAudioConfig(observer, data, offset, audioCodec) {
13322
13422
  let adtsObjectType;
13423
+ let originalAdtsObjectType;
13323
13424
  let adtsExtensionSamplingIndex;
13324
13425
  let adtsChannelConfig;
13325
13426
  let config;
@@ -13327,7 +13428,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13327
13428
  const manifestCodec = audioCodec;
13328
13429
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
13329
13430
  // byte 2
13330
- adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
13431
+ adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
13331
13432
  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
13332
13433
  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
13333
13434
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
@@ -13344,8 +13445,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13344
13445
  // byte 3
13345
13446
  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
13346
13447
  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
13347
- // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
13348
- if (/firefox/i.test(userAgent)) {
13448
+ // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
13449
+ if (/firefox|palemoon/i.test(userAgent)) {
13349
13450
  if (adtsSamplingIndex >= 6) {
13350
13451
  adtsObjectType = 5;
13351
13452
  config = new Array(4);
@@ -13439,6 +13540,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13439
13540
  samplerate: adtsSamplingRates[adtsSamplingIndex],
13440
13541
  channelCount: adtsChannelConfig,
13441
13542
  codec: 'mp4a.40.' + adtsObjectType,
13543
+ parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
13442
13544
  manifestCodec
13443
13545
  };
13444
13546
  }
@@ -13493,7 +13595,8 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
13493
13595
  track.channelCount = config.channelCount;
13494
13596
  track.codec = config.codec;
13495
13597
  track.manifestCodec = config.manifestCodec;
13496
- logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
13598
+ track.parsedCodec = config.parsedCodec;
13599
+ logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
13497
13600
  }
13498
13601
  }
13499
13602
  function getFrameDuration(samplerate) {
@@ -13971,6 +14074,110 @@ class BaseVideoParser {
13971
14074
  logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
13972
14075
  }
13973
14076
  }
14077
+ parseNALu(track, array) {
14078
+ const len = array.byteLength;
14079
+ let state = track.naluState || 0;
14080
+ const lastState = state;
14081
+ const units = [];
14082
+ let i = 0;
14083
+ let value;
14084
+ let overflow;
14085
+ let unitType;
14086
+ let lastUnitStart = -1;
14087
+ let lastUnitType = 0;
14088
+ // logger.log('PES:' + Hex.hexDump(array));
14089
+
14090
+ if (state === -1) {
14091
+ // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
14092
+ lastUnitStart = 0;
14093
+ // NALu type is value read from offset 0
14094
+ lastUnitType = this.getNALuType(array, 0);
14095
+ state = 0;
14096
+ i = 1;
14097
+ }
14098
+ while (i < len) {
14099
+ value = array[i++];
14100
+ // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
14101
+ if (!state) {
14102
+ state = value ? 0 : 1;
14103
+ continue;
14104
+ }
14105
+ if (state === 1) {
14106
+ state = value ? 0 : 2;
14107
+ continue;
14108
+ }
14109
+ // here we have state either equal to 2 or 3
14110
+ if (!value) {
14111
+ state = 3;
14112
+ } else if (value === 1) {
14113
+ overflow = i - state - 1;
14114
+ if (lastUnitStart >= 0) {
14115
+ const unit = {
14116
+ data: array.subarray(lastUnitStart, overflow),
14117
+ type: lastUnitType
14118
+ };
14119
+ // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
14120
+ units.push(unit);
14121
+ } else {
14122
+ // lastUnitStart is undefined => this is the first start code found in this PES packet
14123
+ // first check if start code delimiter is overlapping between 2 PES packets,
14124
+ // ie it started in last packet (lastState not zero)
14125
+ // and ended at the beginning of this PES packet (i <= 4 - lastState)
14126
+ const lastUnit = this.getLastNalUnit(track.samples);
14127
+ if (lastUnit) {
14128
+ if (lastState && i <= 4 - lastState) {
14129
+ // start delimiter overlapping between PES packets
14130
+ // strip start delimiter bytes from the end of last NAL unit
14131
+ // check if lastUnit had a state different from zero
14132
+ if (lastUnit.state) {
14133
+ // strip last bytes
14134
+ lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
14135
+ }
14136
+ }
14137
+ // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
14138
+
14139
+ if (overflow > 0) {
14140
+ // logger.log('first NALU found with overflow:' + overflow);
14141
+ lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
14142
+ lastUnit.state = 0;
14143
+ }
14144
+ }
14145
+ }
14146
+ // check if we can read unit type
14147
+ if (i < len) {
14148
+ unitType = this.getNALuType(array, i);
14149
+ // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
14150
+ lastUnitStart = i;
14151
+ lastUnitType = unitType;
14152
+ state = 0;
14153
+ } else {
14154
+ // not enough byte to read unit type. let's read it on next PES parsing
14155
+ state = -1;
14156
+ }
14157
+ } else {
14158
+ state = 0;
14159
+ }
14160
+ }
14161
+ if (lastUnitStart >= 0 && state >= 0) {
14162
+ const unit = {
14163
+ data: array.subarray(lastUnitStart, len),
14164
+ type: lastUnitType,
14165
+ state: state
14166
+ };
14167
+ units.push(unit);
14168
+ // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
14169
+ }
14170
+ // no NALu found
14171
+ if (units.length === 0) {
14172
+ // append pes.data to previous NAL unit
14173
+ const lastUnit = this.getLastNalUnit(track.samples);
14174
+ if (lastUnit) {
14175
+ lastUnit.data = appendUint8Array(lastUnit.data, array);
14176
+ }
14177
+ }
14178
+ track.naluState = state;
14179
+ return units;
14180
+ }
13974
14181
  }
13975
14182
 
13976
14183
  /**
@@ -14113,21 +14320,171 @@ class ExpGolomb {
14113
14320
  readUInt() {
14114
14321
  return this.readBits(32);
14115
14322
  }
14323
+ }
14324
+
14325
+ class AvcVideoParser extends BaseVideoParser {
14326
+ parsePES(track, textTrack, pes, last, duration) {
14327
+ const units = this.parseNALu(track, pes.data);
14328
+ let VideoSample = this.VideoSample;
14329
+ let push;
14330
+ let spsfound = false;
14331
+ // free pes.data to save up some memory
14332
+ pes.data = null;
14333
+
14334
+ // if new NAL units found and last sample still there, let's push ...
14335
+ // this helps parsing streams with missing AUD (only do this if AUD never found)
14336
+ if (VideoSample && units.length && !track.audFound) {
14337
+ this.pushAccessUnit(VideoSample, track);
14338
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
14339
+ }
14340
+ units.forEach(unit => {
14341
+ var _VideoSample2;
14342
+ switch (unit.type) {
14343
+ // NDR
14344
+ case 1:
14345
+ {
14346
+ let iskey = false;
14347
+ push = true;
14348
+ const data = unit.data;
14349
+ // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
14350
+ if (spsfound && data.length > 4) {
14351
+ // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
14352
+ const sliceType = this.readSliceType(data);
14353
+ // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
14354
+ // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
14355
+ // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
14356
+ // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
14357
+ // if (sliceType === 2 || sliceType === 7) {
14358
+ if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
14359
+ iskey = true;
14360
+ }
14361
+ }
14362
+ if (iskey) {
14363
+ var _VideoSample;
14364
+ // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
14365
+ if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
14366
+ this.pushAccessUnit(VideoSample, track);
14367
+ VideoSample = this.VideoSample = null;
14368
+ }
14369
+ }
14370
+ if (!VideoSample) {
14371
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
14372
+ }
14373
+ VideoSample.frame = true;
14374
+ VideoSample.key = iskey;
14375
+ break;
14376
+ // IDR
14377
+ }
14378
+ case 5:
14379
+ push = true;
14380
+ // handle PES not starting with AUD
14381
+ // if we have frame data already, that cannot belong to the same frame, so force a push
14382
+ if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
14383
+ this.pushAccessUnit(VideoSample, track);
14384
+ VideoSample = this.VideoSample = null;
14385
+ }
14386
+ if (!VideoSample) {
14387
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
14388
+ }
14389
+ VideoSample.key = true;
14390
+ VideoSample.frame = true;
14391
+ break;
14392
+ // SEI
14393
+ case 6:
14394
+ {
14395
+ push = true;
14396
+ parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
14397
+ break;
14398
+ // SPS
14399
+ }
14400
+ case 7:
14401
+ {
14402
+ var _track$pixelRatio, _track$pixelRatio2;
14403
+ push = true;
14404
+ spsfound = true;
14405
+ const sps = unit.data;
14406
+ const config = this.readSPS(sps);
14407
+ if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) {
14408
+ track.width = config.width;
14409
+ track.height = config.height;
14410
+ track.pixelRatio = config.pixelRatio;
14411
+ track.sps = [sps];
14412
+ track.duration = duration;
14413
+ const codecarray = sps.subarray(1, 4);
14414
+ let codecstring = 'avc1.';
14415
+ for (let i = 0; i < 3; i++) {
14416
+ let h = codecarray[i].toString(16);
14417
+ if (h.length < 2) {
14418
+ h = '0' + h;
14419
+ }
14420
+ codecstring += h;
14421
+ }
14422
+ track.codec = codecstring;
14423
+ }
14424
+ break;
14425
+ }
14426
+ // PPS
14427
+ case 8:
14428
+ push = true;
14429
+ track.pps = [unit.data];
14430
+ break;
14431
+ // AUD
14432
+ case 9:
14433
+ push = true;
14434
+ track.audFound = true;
14435
+ if (VideoSample) {
14436
+ this.pushAccessUnit(VideoSample, track);
14437
+ }
14438
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
14439
+ break;
14440
+ // Filler Data
14441
+ case 12:
14442
+ push = true;
14443
+ break;
14444
+ default:
14445
+ push = false;
14446
+ if (VideoSample) {
14447
+ VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
14448
+ }
14449
+ break;
14450
+ }
14451
+ if (VideoSample && push) {
14452
+ const units = VideoSample.units;
14453
+ units.push(unit);
14454
+ }
14455
+ });
14456
+ // if last PES packet, push samples
14457
+ if (last && VideoSample) {
14458
+ this.pushAccessUnit(VideoSample, track);
14459
+ this.VideoSample = null;
14460
+ }
14461
+ }
14462
+ getNALuType(data, offset) {
14463
+ return data[offset] & 0x1f;
14464
+ }
14465
+ readSliceType(data) {
14466
+ const eg = new ExpGolomb(data);
14467
+ // skip NALu type
14468
+ eg.readUByte();
14469
+ // discard first_mb_in_slice
14470
+ eg.readUEG();
14471
+ // return slice_type
14472
+ return eg.readUEG();
14473
+ }
14116
14474
 
14117
14475
  /**
14118
- * Advance the ExpGolomb decoder past a scaling list. The scaling
14119
- * list is optionally transmitted as part of a sequence parameter
14476
+ * The scaling list is optionally transmitted as part of a sequence parameter
14120
14477
  * set and is not relevant to transmuxing.
14121
14478
  * @param count the number of entries in this scaling list
14122
14479
  * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
14123
14480
  */
14124
- skipScalingList(count) {
14481
+ skipScalingList(count, reader) {
14125
14482
  let lastScale = 8;
14126
14483
  let nextScale = 8;
14127
14484
  let deltaScale;
14128
14485
  for (let j = 0; j < count; j++) {
14129
14486
  if (nextScale !== 0) {
14130
- deltaScale = this.readEG();
14487
+ deltaScale = reader.readEG();
14131
14488
  nextScale = (lastScale + deltaScale + 256) % 256;
14132
14489
  }
14133
14490
  lastScale = nextScale === 0 ? lastScale : nextScale;
@@ -14142,7 +14499,8 @@ class ExpGolomb {
14142
14499
  * sequence parameter set, including the dimensions of the
14143
14500
  * associated video frames.
14144
14501
  */
14145
- readSPS() {
14502
+ readSPS(sps) {
14503
+ const eg = new ExpGolomb(sps);
14146
14504
  let frameCropLeftOffset = 0;
14147
14505
  let frameCropRightOffset = 0;
14148
14506
  let frameCropTopOffset = 0;
@@ -14150,13 +14508,13 @@ class ExpGolomb {
14150
14508
  let numRefFramesInPicOrderCntCycle;
14151
14509
  let scalingListCount;
14152
14510
  let i;
14153
- const readUByte = this.readUByte.bind(this);
14154
- const readBits = this.readBits.bind(this);
14155
- const readUEG = this.readUEG.bind(this);
14156
- const readBoolean = this.readBoolean.bind(this);
14157
- const skipBits = this.skipBits.bind(this);
14158
- const skipEG = this.skipEG.bind(this);
14159
- const skipUEG = this.skipUEG.bind(this);
14511
+ const readUByte = eg.readUByte.bind(eg);
14512
+ const readBits = eg.readBits.bind(eg);
14513
+ const readUEG = eg.readUEG.bind(eg);
14514
+ const readBoolean = eg.readBoolean.bind(eg);
14515
+ const skipBits = eg.skipBits.bind(eg);
14516
+ const skipEG = eg.skipEG.bind(eg);
14517
+ const skipUEG = eg.skipUEG.bind(eg);
14160
14518
  const skipScalingList = this.skipScalingList.bind(this);
14161
14519
  readUByte();
14162
14520
  const profileIdc = readUByte(); // profile_idc
@@ -14181,9 +14539,9 @@ class ExpGolomb {
14181
14539
  if (readBoolean()) {
14182
14540
  // seq_scaling_list_present_flag[ i ]
14183
14541
  if (i < 6) {
14184
- skipScalingList(16);
14542
+ skipScalingList(16, eg);
14185
14543
  } else {
14186
- skipScalingList(64);
14544
+ skipScalingList(64, eg);
14187
14545
  }
14188
14546
  }
14189
14547
  }
@@ -14288,258 +14646,6 @@ class ExpGolomb {
14288
14646
  pixelRatio: pixelRatio
14289
14647
  };
14290
14648
  }
14291
- readSliceType() {
14292
- // skip NALu type
14293
- this.readUByte();
14294
- // discard first_mb_in_slice
14295
- this.readUEG();
14296
- // return slice_type
14297
- return this.readUEG();
14298
- }
14299
- }
14300
-
14301
- class AvcVideoParser extends BaseVideoParser {
14302
- parseAVCPES(track, textTrack, pes, last, duration) {
14303
- const units = this.parseAVCNALu(track, pes.data);
14304
- let VideoSample = this.VideoSample;
14305
- let push;
14306
- let spsfound = false;
14307
- // free pes.data to save up some memory
14308
- pes.data = null;
14309
-
14310
- // if new NAL units found and last sample still there, let's push ...
14311
- // this helps parsing streams with missing AUD (only do this if AUD never found)
14312
- if (VideoSample && units.length && !track.audFound) {
14313
- this.pushAccessUnit(VideoSample, track);
14314
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
14315
- }
14316
- units.forEach(unit => {
14317
- var _VideoSample2;
14318
- switch (unit.type) {
14319
- // NDR
14320
- case 1:
14321
- {
14322
- let iskey = false;
14323
- push = true;
14324
- const data = unit.data;
14325
- // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
14326
- if (spsfound && data.length > 4) {
14327
- // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
14328
- const sliceType = new ExpGolomb(data).readSliceType();
14329
- // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
14330
- // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
14331
- // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
14332
- // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
14333
- // if (sliceType === 2 || sliceType === 7) {
14334
- if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
14335
- iskey = true;
14336
- }
14337
- }
14338
- if (iskey) {
14339
- var _VideoSample;
14340
- // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
14341
- if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
14342
- this.pushAccessUnit(VideoSample, track);
14343
- VideoSample = this.VideoSample = null;
14344
- }
14345
- }
14346
- if (!VideoSample) {
14347
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
14348
- }
14349
- VideoSample.frame = true;
14350
- VideoSample.key = iskey;
14351
- break;
14352
- // IDR
14353
- }
14354
- case 5:
14355
- push = true;
14356
- // handle PES not starting with AUD
14357
- // if we have frame data already, that cannot belong to the same frame, so force a push
14358
- if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
14359
- this.pushAccessUnit(VideoSample, track);
14360
- VideoSample = this.VideoSample = null;
14361
- }
14362
- if (!VideoSample) {
14363
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
14364
- }
14365
- VideoSample.key = true;
14366
- VideoSample.frame = true;
14367
- break;
14368
- // SEI
14369
- case 6:
14370
- {
14371
- push = true;
14372
- parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
14373
- break;
14374
- // SPS
14375
- }
14376
- case 7:
14377
- {
14378
- var _track$pixelRatio, _track$pixelRatio2;
14379
- push = true;
14380
- spsfound = true;
14381
- const sps = unit.data;
14382
- const expGolombDecoder = new ExpGolomb(sps);
14383
- const config = expGolombDecoder.readSPS();
14384
- if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) {
14385
- track.width = config.width;
14386
- track.height = config.height;
14387
- track.pixelRatio = config.pixelRatio;
14388
- track.sps = [sps];
14389
- track.duration = duration;
14390
- const codecarray = sps.subarray(1, 4);
14391
- let codecstring = 'avc1.';
14392
- for (let i = 0; i < 3; i++) {
14393
- let h = codecarray[i].toString(16);
14394
- if (h.length < 2) {
14395
- h = '0' + h;
14396
- }
14397
- codecstring += h;
14398
- }
14399
- track.codec = codecstring;
14400
- }
14401
- break;
14402
- }
14403
- // PPS
14404
- case 8:
14405
- push = true;
14406
- track.pps = [unit.data];
14407
- break;
14408
- // AUD
14409
- case 9:
14410
- push = true;
14411
- track.audFound = true;
14412
- if (VideoSample) {
14413
- this.pushAccessUnit(VideoSample, track);
14414
- }
14415
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
14416
- break;
14417
- // Filler Data
14418
- case 12:
14419
- push = true;
14420
- break;
14421
- default:
14422
- push = false;
14423
- if (VideoSample) {
14424
- VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
14425
- }
14426
- break;
14427
- }
14428
- if (VideoSample && push) {
14429
- const units = VideoSample.units;
14430
- units.push(unit);
14431
- }
14432
- });
14433
- // if last PES packet, push samples
14434
- if (last && VideoSample) {
14435
- this.pushAccessUnit(VideoSample, track);
14436
- this.VideoSample = null;
14437
- }
14438
- }
14439
- parseAVCNALu(track, array) {
14440
- const len = array.byteLength;
14441
- let state = track.naluState || 0;
14442
- const lastState = state;
14443
- const units = [];
14444
- let i = 0;
14445
- let value;
14446
- let overflow;
14447
- let unitType;
14448
- let lastUnitStart = -1;
14449
- let lastUnitType = 0;
14450
- // logger.log('PES:' + Hex.hexDump(array));
14451
-
14452
- if (state === -1) {
14453
- // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
14454
- lastUnitStart = 0;
14455
- // NALu type is value read from offset 0
14456
- lastUnitType = array[0] & 0x1f;
14457
- state = 0;
14458
- i = 1;
14459
- }
14460
- while (i < len) {
14461
- value = array[i++];
14462
- // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
14463
- if (!state) {
14464
- state = value ? 0 : 1;
14465
- continue;
14466
- }
14467
- if (state === 1) {
14468
- state = value ? 0 : 2;
14469
- continue;
14470
- }
14471
- // here we have state either equal to 2 or 3
14472
- if (!value) {
14473
- state = 3;
14474
- } else if (value === 1) {
14475
- overflow = i - state - 1;
14476
- if (lastUnitStart >= 0) {
14477
- const unit = {
14478
- data: array.subarray(lastUnitStart, overflow),
14479
- type: lastUnitType
14480
- };
14481
- // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
14482
- units.push(unit);
14483
- } else {
14484
- // lastUnitStart is undefined => this is the first start code found in this PES packet
14485
- // first check if start code delimiter is overlapping between 2 PES packets,
14486
- // ie it started in last packet (lastState not zero)
14487
- // and ended at the beginning of this PES packet (i <= 4 - lastState)
14488
- const lastUnit = this.getLastNalUnit(track.samples);
14489
- if (lastUnit) {
14490
- if (lastState && i <= 4 - lastState) {
14491
- // start delimiter overlapping between PES packets
14492
- // strip start delimiter bytes from the end of last NAL unit
14493
- // check if lastUnit had a state different from zero
14494
- if (lastUnit.state) {
14495
- // strip last bytes
14496
- lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
14497
- }
14498
- }
14499
- // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
14500
-
14501
- if (overflow > 0) {
14502
- // logger.log('first NALU found with overflow:' + overflow);
14503
- lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
14504
- lastUnit.state = 0;
14505
- }
14506
- }
14507
- }
14508
- // check if we can read unit type
14509
- if (i < len) {
14510
- unitType = array[i] & 0x1f;
14511
- // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
14512
- lastUnitStart = i;
14513
- lastUnitType = unitType;
14514
- state = 0;
14515
- } else {
14516
- // not enough byte to read unit type. let's read it on next PES parsing
14517
- state = -1;
14518
- }
14519
- } else {
14520
- state = 0;
14521
- }
14522
- }
14523
- if (lastUnitStart >= 0 && state >= 0) {
14524
- const unit = {
14525
- data: array.subarray(lastUnitStart, len),
14526
- type: lastUnitType,
14527
- state: state
14528
- };
14529
- units.push(unit);
14530
- // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
14531
- }
14532
- // no NALu found
14533
- if (units.length === 0) {
14534
- // append pes.data to previous NAL unit
14535
- const lastUnit = this.getLastNalUnit(track.samples);
14536
- if (lastUnit) {
14537
- lastUnit.data = appendUint8Array(lastUnit.data, array);
14538
- }
14539
- }
14540
- track.naluState = state;
14541
- return units;
14542
- }
14543
14649
  }
14544
14650
 
14545
14651
  /**
@@ -14556,7 +14662,7 @@ class SampleAesDecrypter {
14556
14662
  });
14557
14663
  }
14558
14664
  decryptBuffer(encryptedData) {
14559
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
14665
+ return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
14560
14666
  }
14561
14667
 
14562
14668
  // AAC - encrypt all full 16 bytes blocks starting from offset 16
@@ -14670,7 +14776,7 @@ class TSDemuxer {
14670
14776
  this.observer = observer;
14671
14777
  this.config = config;
14672
14778
  this.typeSupported = typeSupported;
14673
- this.videoParser = new AvcVideoParser();
14779
+ this.videoParser = null;
14674
14780
  }
14675
14781
  static probe(data) {
14676
14782
  const syncOffset = TSDemuxer.syncOffset(data);
@@ -14835,7 +14941,16 @@ class TSDemuxer {
14835
14941
  case videoPid:
14836
14942
  if (stt) {
14837
14943
  if (videoData && (pes = parsePES(videoData))) {
14838
- this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
14944
+ if (this.videoParser === null) {
14945
+ switch (videoTrack.segmentCodec) {
14946
+ case 'avc':
14947
+ this.videoParser = new AvcVideoParser();
14948
+ break;
14949
+ }
14950
+ }
14951
+ if (this.videoParser !== null) {
14952
+ this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
14953
+ }
14839
14954
  }
14840
14955
  videoData = {
14841
14956
  data: [],
@@ -14997,8 +15112,17 @@ class TSDemuxer {
14997
15112
  // try to parse last PES packets
14998
15113
  let pes;
14999
15114
  if (videoData && (pes = parsePES(videoData))) {
15000
- this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
15001
- videoTrack.pesData = null;
15115
+ if (this.videoParser === null) {
15116
+ switch (videoTrack.segmentCodec) {
15117
+ case 'avc':
15118
+ this.videoParser = new AvcVideoParser();
15119
+ break;
15120
+ }
15121
+ }
15122
+ if (this.videoParser !== null) {
15123
+ this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
15124
+ videoTrack.pesData = null;
15125
+ }
15002
15126
  } else {
15003
15127
  // either avcData null or PES truncated, keep it for next frag parsing
15004
15128
  videoTrack.pesData = videoData;
@@ -15301,7 +15425,10 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
15301
15425
  logger.warn('Unsupported EC-3 in M2TS found');
15302
15426
  break;
15303
15427
  case 0x24:
15304
- logger.warn('Unsupported HEVC in M2TS found');
15428
+ // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
15429
+ {
15430
+ logger.warn('Unsupported HEVC in M2TS found');
15431
+ }
15305
15432
  break;
15306
15433
  }
15307
15434
  // move to the next table entry
@@ -15524,6 +15651,8 @@ class MP4 {
15524
15651
  avc1: [],
15525
15652
  // codingname
15526
15653
  avcC: [],
15654
+ hvc1: [],
15655
+ hvcC: [],
15527
15656
  btrt: [],
15528
15657
  dinf: [],
15529
15658
  dref: [],
@@ -15948,8 +16077,10 @@ class MP4 {
15948
16077
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
15949
16078
  }
15950
16079
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
15951
- } else {
16080
+ } else if (track.segmentCodec === 'avc') {
15952
16081
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
16082
+ } else {
16083
+ return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
15953
16084
  }
15954
16085
  }
15955
16086
  static tkhd(track) {
@@ -16087,6 +16218,84 @@ class MP4 {
16087
16218
  const result = appendUint8Array(MP4.FTYP, movie);
16088
16219
  return result;
16089
16220
  }
16221
+ static hvc1(track) {
16222
+ const ps = track.params;
16223
+ const units = [track.vps, track.sps, track.pps];
16224
+ const NALuLengthSize = 4;
16225
+ const config = new Uint8Array([0x01, ps.general_profile_space << 6 | (ps.general_tier_flag ? 32 : 0) | ps.general_profile_idc, ps.general_profile_compatibility_flags[0], ps.general_profile_compatibility_flags[1], ps.general_profile_compatibility_flags[2], ps.general_profile_compatibility_flags[3], ps.general_constraint_indicator_flags[0], ps.general_constraint_indicator_flags[1], ps.general_constraint_indicator_flags[2], ps.general_constraint_indicator_flags[3], ps.general_constraint_indicator_flags[4], ps.general_constraint_indicator_flags[5], ps.general_level_idc, 240 | ps.min_spatial_segmentation_idc >> 8, 255 & ps.min_spatial_segmentation_idc, 252 | ps.parallelismType, 252 | ps.chroma_format_idc, 248 | ps.bit_depth_luma_minus8, 248 | ps.bit_depth_chroma_minus8, 0x00, parseInt(ps.frame_rate.fps), NALuLengthSize - 1 | ps.temporal_id_nested << 2 | ps.num_temporal_layers << 3 | (ps.frame_rate.fixed ? 64 : 0), units.length]);
16226
+
16227
+ // compute hvcC size in bytes
16228
+ let length = config.length;
16229
+ for (let i = 0; i < units.length; i += 1) {
16230
+ length += 3;
16231
+ for (let j = 0; j < units[i].length; j += 1) {
16232
+ length += 2 + units[i][j].length;
16233
+ }
16234
+ }
16235
+ const hvcC = new Uint8Array(length);
16236
+ hvcC.set(config, 0);
16237
+ length = config.length;
16238
+ // append parameter set units: one vps, one or more sps and pps
16239
+ const iMax = units.length - 1;
16240
+ for (let i = 0; i < units.length; i += 1) {
16241
+ hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
16242
+ length += 3;
16243
+ for (let j = 0; j < units[i].length; j += 1) {
16244
+ hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
16245
+ length += 2;
16246
+ hvcC.set(units[i][j], length);
16247
+ length += units[i][j].length;
16248
+ }
16249
+ }
16250
+ const hvcc = MP4.box(MP4.types.hvcC, hvcC);
16251
+ const width = track.width;
16252
+ const height = track.height;
16253
+ const hSpacing = track.pixelRatio[0];
16254
+ const vSpacing = track.pixelRatio[1];
16255
+ return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
16256
+ // reserved
16257
+ 0x00, 0x00, 0x00,
16258
+ // reserved
16259
+ 0x00, 0x01,
16260
+ // data_reference_index
16261
+ 0x00, 0x00,
16262
+ // pre_defined
16263
+ 0x00, 0x00,
16264
+ // reserved
16265
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
16266
+ // pre_defined
16267
+ width >> 8 & 0xff, width & 0xff,
16268
+ // width
16269
+ height >> 8 & 0xff, height & 0xff,
16270
+ // height
16271
+ 0x00, 0x48, 0x00, 0x00,
16272
+ // horizresolution
16273
+ 0x00, 0x48, 0x00, 0x00,
16274
+ // vertresolution
16275
+ 0x00, 0x00, 0x00, 0x00,
16276
+ // reserved
16277
+ 0x00, 0x01,
16278
+ // frame_count
16279
+ 0x12, 0x64, 0x61, 0x69, 0x6c,
16280
+ // dailymotion/hls.js
16281
+ 0x79, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x68, 0x6c, 0x73, 0x2e, 0x6a, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
16282
+ // compressorname
16283
+ 0x00, 0x18,
16284
+ // depth = 24
16285
+ 0x11, 0x11]),
16286
+ // pre_defined = -1
16287
+ hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
16288
+ // bufferSizeDB
16289
+ 0x00, 0x2d, 0xc6, 0xc0,
16290
+ // maxBitrate
16291
+ 0x00, 0x2d, 0xc6, 0xc0])),
16292
+ // avgBitrate
16293
+ MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
16294
+ // hSpacing
16295
+ hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
16296
+ // vSpacing
16297
+ vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
16298
+ }
16090
16299
  }
16091
16300
  MP4.types = void 0;
16092
16301
  MP4.HDLR_TYPES = void 0;
@@ -16462,9 +16671,9 @@ class MP4Remuxer {
16462
16671
  const foundOverlap = delta < -1;
16463
16672
  if (foundHole || foundOverlap) {
16464
16673
  if (foundHole) {
16465
- logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
16674
+ logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
16466
16675
  } else {
16467
- logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
16676
+ logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
16468
16677
  }
16469
16678
  if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
16470
16679
  firstDTS = nextAvcDts;
@@ -16473,12 +16682,24 @@ class MP4Remuxer {
16473
16682
  inputSamples[0].dts = firstDTS;
16474
16683
  inputSamples[0].pts = firstPTS;
16475
16684
  } else {
16685
+ let isPTSOrderRetained = true;
16476
16686
  for (let i = 0; i < inputSamples.length; i++) {
16477
- if (inputSamples[i].dts > firstPTS) {
16687
+ if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
16478
16688
  break;
16479
16689
  }
16690
+ const prevPTS = inputSamples[i].pts;
16480
16691
  inputSamples[i].dts -= delta;
16481
16692
  inputSamples[i].pts -= delta;
16693
+
16694
+ // check to see if this sample's PTS order has changed
16695
+ // relative to the next one
16696
+ if (i < inputSamples.length - 1) {
16697
+ const nextSamplePTS = inputSamples[i + 1].pts;
16698
+ const currentSamplePTS = inputSamples[i].pts;
16699
+ const currentOrder = nextSamplePTS <= currentSamplePTS;
16700
+ const prevOrder = nextSamplePTS <= prevPTS;
16701
+ isPTSOrderRetained = currentOrder == prevOrder;
16702
+ }
16482
16703
  }
16483
16704
  }
16484
16705
  logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
@@ -16626,7 +16847,7 @@ class MP4Remuxer {
16626
16847
  }
16627
16848
  }
16628
16849
  }
16629
- // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
16850
+ // next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
16630
16851
  mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
16631
16852
  this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
16632
16853
  this.videoSampleDuration = mp4SampleDuration;
@@ -16759,7 +16980,7 @@ class MP4Remuxer {
16759
16980
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
16760
16981
  for (let j = 0; j < missing; j++) {
16761
16982
  const newStamp = Math.max(nextPts, 0);
16762
- let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
16983
+ let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
16763
16984
  if (!fillFrame) {
16764
16985
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
16765
16986
  fillFrame = sample.unit.subarray();
@@ -16887,7 +17108,7 @@ class MP4Remuxer {
16887
17108
  // samples count of this segment's duration
16888
17109
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
16889
17110
  // silent frame
16890
- const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
17111
+ const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
16891
17112
  logger.warn('[mp4-remuxer]: remux empty Audio');
16892
17113
  // Can't remux if we can't generate a silent frame...
16893
17114
  if (!silentFrame) {
@@ -17278,13 +17499,15 @@ class Transmuxer {
17278
17499
  initSegmentData
17279
17500
  } = transmuxConfig;
17280
17501
  const keyData = getEncryptionType(uintData, decryptdata);
17281
- if (keyData && keyData.method === 'AES-128') {
17502
+ if (keyData && isFullSegmentEncryption(keyData.method)) {
17282
17503
  const decrypter = this.getDecrypter();
17504
+ const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
17505
+
17283
17506
  // Software decryption is synchronous; webCrypto is not
17284
17507
  if (decrypter.isSync()) {
17285
17508
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
17286
17509
  // data is handled in the flush() call
17287
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
17510
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
17288
17511
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
17289
17512
  const loadingParts = chunkMeta.part > -1;
17290
17513
  if (loadingParts) {
@@ -17296,7 +17519,7 @@ class Transmuxer {
17296
17519
  }
17297
17520
  uintData = new Uint8Array(decryptedData);
17298
17521
  } else {
17299
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
17522
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
17300
17523
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
17301
17524
  // the decrypted data has been transmuxed
17302
17525
  const result = this.push(decryptedData, null, chunkMeta);
@@ -17950,14 +18173,7 @@ class TransmuxerInterface {
17950
18173
  this.observer = new EventEmitter();
17951
18174
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
17952
18175
  this.observer.on(Events.ERROR, forwardMessage);
17953
- const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
17954
- isTypeSupported: () => false
17955
- };
17956
- const m2tsTypeSupported = {
17957
- mpeg: MediaSource.isTypeSupported('audio/mpeg'),
17958
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
17959
- ac3: false
17960
- };
18176
+ const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
17961
18177
 
17962
18178
  // navigator.vendor is not always available in Web Worker
17963
18179
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -18221,8 +18437,9 @@ const STALL_MINIMUM_DURATION_MS = 250;
18221
18437
  const MAX_START_GAP_JUMP = 2.0;
18222
18438
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
18223
18439
  const SKIP_BUFFER_RANGE_START = 0.05;
18224
- class GapController {
18440
+ class GapController extends Logger {
18225
18441
  constructor(config, media, fragmentTracker, hls) {
18442
+ super('gap-controller', hls.logger);
18226
18443
  this.config = void 0;
18227
18444
  this.media = null;
18228
18445
  this.fragmentTracker = void 0;
@@ -18232,6 +18449,7 @@ class GapController {
18232
18449
  this.stalled = null;
18233
18450
  this.moved = false;
18234
18451
  this.seeking = false;
18452
+ this.ended = 0;
18235
18453
  this.config = config;
18236
18454
  this.media = media;
18237
18455
  this.fragmentTracker = fragmentTracker;
@@ -18249,7 +18467,7 @@ class GapController {
18249
18467
  *
18250
18468
  * @param lastCurrentTime - Previously read playhead position
18251
18469
  */
18252
- poll(lastCurrentTime, activeFrag) {
18470
+ poll(lastCurrentTime, activeFrag, levelDetails, state) {
18253
18471
  const {
18254
18472
  config,
18255
18473
  media,
@@ -18268,6 +18486,7 @@ class GapController {
18268
18486
 
18269
18487
  // The playhead is moving, no-op
18270
18488
  if (currentTime !== lastCurrentTime) {
18489
+ this.ended = 0;
18271
18490
  this.moved = true;
18272
18491
  if (!seeking) {
18273
18492
  this.nudgeRetry = 0;
@@ -18276,7 +18495,7 @@ class GapController {
18276
18495
  // The playhead is now moving, but was previously stalled
18277
18496
  if (this.stallReported) {
18278
18497
  const _stalledDuration = self.performance.now() - stalled;
18279
- logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
18498
+ this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
18280
18499
  this.stallReported = false;
18281
18500
  }
18282
18501
  this.stalled = null;
@@ -18312,7 +18531,6 @@ class GapController {
18312
18531
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
18313
18532
  // The addition poll gives the browser a chance to jump the gap for us
18314
18533
  if (!this.moved && this.stalled !== null) {
18315
- var _level$details;
18316
18534
  // There is no playable buffer (seeked, waiting for buffer)
18317
18535
  const isBuffered = bufferInfo.len > 0;
18318
18536
  if (!isBuffered && !nextStart) {
@@ -18324,9 +18542,8 @@ class GapController {
18324
18542
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
18325
18543
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
18326
18544
  // that begins over 1 target duration after the video start position.
18327
- const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
18328
- const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
18329
- const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
18545
+ const isLive = !!(levelDetails != null && levelDetails.live);
18546
+ const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
18330
18547
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
18331
18548
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
18332
18549
  if (!media.paused) {
@@ -18344,6 +18561,17 @@ class GapController {
18344
18561
  }
18345
18562
  const stalledDuration = tnow - stalled;
18346
18563
  if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
18564
+ // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
18565
+ if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
18566
+ if (stalledDuration < 1000 || this.ended) {
18567
+ return;
18568
+ }
18569
+ this.ended = currentTime;
18570
+ this.hls.trigger(Events.MEDIA_ENDED, {
18571
+ stalled: true
18572
+ });
18573
+ return;
18574
+ }
18347
18575
  // Report stalling after trying to fix
18348
18576
  this._reportStall(bufferInfo);
18349
18577
  if (!this.media) {
@@ -18387,7 +18615,7 @@ class GapController {
18387
18615
  // needs to cross some sort of threshold covering all source-buffers content
18388
18616
  // to start playing properly.
18389
18617
  if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
18390
- logger.warn('Trying to nudge playhead over buffer-hole');
18618
+ this.warn('Trying to nudge playhead over buffer-hole');
18391
18619
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
18392
18620
  // We only try to jump the hole if it's under the configured size
18393
18621
  // Reset stalled so to rearm watchdog timer
@@ -18411,7 +18639,7 @@ class GapController {
18411
18639
  // Report stalled error once
18412
18640
  this.stallReported = true;
18413
18641
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
18414
- logger.warn(error.message);
18642
+ this.warn(error.message);
18415
18643
  hls.trigger(Events.ERROR, {
18416
18644
  type: ErrorTypes.MEDIA_ERROR,
18417
18645
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -18479,7 +18707,7 @@ class GapController {
18479
18707
  }
18480
18708
  }
18481
18709
  const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
18482
- logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
18710
+ this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
18483
18711
  this.moved = true;
18484
18712
  this.stalled = null;
18485
18713
  media.currentTime = targetTime;
@@ -18520,7 +18748,7 @@ class GapController {
18520
18748
  const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
18521
18749
  // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
18522
18750
  const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
18523
- logger.warn(error.message);
18751
+ this.warn(error.message);
18524
18752
  media.currentTime = targetTime;
18525
18753
  hls.trigger(Events.ERROR, {
18526
18754
  type: ErrorTypes.MEDIA_ERROR,
@@ -18530,7 +18758,7 @@ class GapController {
18530
18758
  });
18531
18759
  } else {
18532
18760
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
18533
- logger.error(error.message);
18761
+ this.error(error.message);
18534
18762
  hls.trigger(Events.ERROR, {
18535
18763
  type: ErrorTypes.MEDIA_ERROR,
18536
18764
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -18545,7 +18773,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
18545
18773
 
18546
18774
  class StreamController extends BaseStreamController {
18547
18775
  constructor(hls, fragmentTracker, keyLoader) {
18548
- super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
18776
+ super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
18549
18777
  this.audioCodecSwap = false;
18550
18778
  this.gapController = null;
18551
18779
  this.level = -1;
@@ -18553,27 +18781,43 @@ class StreamController extends BaseStreamController {
18553
18781
  this.altAudio = false;
18554
18782
  this.audioOnly = false;
18555
18783
  this.fragPlaying = null;
18556
- this.onvplaying = null;
18557
- this.onvseeked = null;
18558
18784
  this.fragLastKbps = 0;
18559
18785
  this.couldBacktrack = false;
18560
18786
  this.backtrackFragment = null;
18561
18787
  this.audioCodecSwitch = false;
18562
18788
  this.videoBuffer = null;
18563
- this._registerListeners();
18789
+ this.onMediaPlaying = () => {
18790
+ // tick to speed up FRAG_CHANGED triggering
18791
+ this.tick();
18792
+ };
18793
+ this.onMediaSeeked = () => {
18794
+ const media = this.media;
18795
+ const currentTime = media ? media.currentTime : null;
18796
+ if (isFiniteNumber(currentTime)) {
18797
+ this.log(`Media seeked to ${currentTime.toFixed(3)}`);
18798
+ }
18799
+
18800
+ // If seeked was issued before buffer was appended do not tick immediately
18801
+ const bufferInfo = this.getMainFwdBufferInfo();
18802
+ if (bufferInfo === null || bufferInfo.len === 0) {
18803
+ this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
18804
+ return;
18805
+ }
18806
+
18807
+ // tick to speed up FRAG_CHANGED triggering
18808
+ this.tick();
18809
+ };
18810
+ this.registerListeners();
18564
18811
  }
18565
- _registerListeners() {
18812
+ registerListeners() {
18813
+ super.registerListeners();
18566
18814
  const {
18567
18815
  hls
18568
18816
  } = this;
18569
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
18570
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
18571
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18572
18817
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18573
18818
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
18574
18819
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
18575
18820
  hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
18576
- hls.on(Events.ERROR, this.onError, this);
18577
18821
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
18578
18822
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
18579
18823
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -18581,17 +18825,14 @@ class StreamController extends BaseStreamController {
18581
18825
  hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
18582
18826
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
18583
18827
  }
18584
- _unregisterListeners() {
18828
+ unregisterListeners() {
18829
+ super.unregisterListeners();
18585
18830
  const {
18586
18831
  hls
18587
18832
  } = this;
18588
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
18589
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
18590
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18591
18833
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18592
18834
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
18593
18835
  hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
18594
- hls.off(Events.ERROR, this.onError, this);
18595
18836
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
18596
18837
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
18597
18838
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -18600,7 +18841,9 @@ class StreamController extends BaseStreamController {
18600
18841
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
18601
18842
  }
18602
18843
  onHandlerDestroying() {
18603
- this._unregisterListeners();
18844
+ // @ts-ignore
18845
+ this.onMediaPlaying = this.onMediaSeeked = null;
18846
+ this.unregisterListeners();
18604
18847
  super.onHandlerDestroying();
18605
18848
  }
18606
18849
  startLoad(startPosition) {
@@ -18720,7 +18963,7 @@ class StreamController extends BaseStreamController {
18720
18963
  if (this.altAudio && this.audioOnly) {
18721
18964
  return;
18722
18965
  }
18723
- if (!(levels != null && levels[level])) {
18966
+ if (!this.buffering || !(levels != null && levels[level])) {
18724
18967
  return;
18725
18968
  }
18726
18969
  const levelInfo = levels[level];
@@ -18928,20 +19171,17 @@ class StreamController extends BaseStreamController {
18928
19171
  onMediaAttached(event, data) {
18929
19172
  super.onMediaAttached(event, data);
18930
19173
  const media = data.media;
18931
- this.onvplaying = this.onMediaPlaying.bind(this);
18932
- this.onvseeked = this.onMediaSeeked.bind(this);
18933
- media.addEventListener('playing', this.onvplaying);
18934
- media.addEventListener('seeked', this.onvseeked);
19174
+ media.addEventListener('playing', this.onMediaPlaying);
19175
+ media.addEventListener('seeked', this.onMediaSeeked);
18935
19176
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
18936
19177
  }
18937
19178
  onMediaDetaching() {
18938
19179
  const {
18939
19180
  media
18940
19181
  } = this;
18941
- if (media && this.onvplaying && this.onvseeked) {
18942
- media.removeEventListener('playing', this.onvplaying);
18943
- media.removeEventListener('seeked', this.onvseeked);
18944
- this.onvplaying = this.onvseeked = null;
19182
+ if (media) {
19183
+ media.removeEventListener('playing', this.onMediaPlaying);
19184
+ media.removeEventListener('seeked', this.onMediaSeeked);
18945
19185
  this.videoBuffer = null;
18946
19186
  }
18947
19187
  this.fragPlaying = null;
@@ -18951,27 +19191,6 @@ class StreamController extends BaseStreamController {
18951
19191
  }
18952
19192
  super.onMediaDetaching();
18953
19193
  }
18954
- onMediaPlaying() {
18955
- // tick to speed up FRAG_CHANGED triggering
18956
- this.tick();
18957
- }
18958
- onMediaSeeked() {
18959
- const media = this.media;
18960
- const currentTime = media ? media.currentTime : null;
18961
- if (isFiniteNumber(currentTime)) {
18962
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
18963
- }
18964
-
18965
- // If seeked was issued before buffer was appended do not tick immediately
18966
- const bufferInfo = this.getMainFwdBufferInfo();
18967
- if (bufferInfo === null || bufferInfo.len === 0) {
18968
- this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
18969
- return;
18970
- }
18971
-
18972
- // tick to speed up FRAG_CHANGED triggering
18973
- this.tick();
18974
- }
18975
19194
  onManifestLoading() {
18976
19195
  // reset buffer on manifest loading
18977
19196
  this.log('Trigger BUFFER_RESET');
@@ -19263,8 +19482,10 @@ class StreamController extends BaseStreamController {
19263
19482
  }
19264
19483
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
19265
19484
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
19266
- const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
19267
- gapController.poll(this.lastCurrentTime, activeFrag);
19485
+ const state = this.state;
19486
+ const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
19487
+ const levelDetails = this.getLevelDetails();
19488
+ gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
19268
19489
  }
19269
19490
  this.lastCurrentTime = media.currentTime;
19270
19491
  }
@@ -19702,7 +19923,7 @@ class Hls {
19702
19923
  * Get the video-dev/hls.js package version.
19703
19924
  */
19704
19925
  static get version() {
19705
- return "1.5.4";
19926
+ return "1.5.5-0.canary.9978";
19706
19927
  }
19707
19928
 
19708
19929
  /**
@@ -19765,9 +19986,12 @@ class Hls {
19765
19986
  * The configuration object provided on player instantiation.
19766
19987
  */
19767
19988
  this.userConfig = void 0;
19989
+ /**
19990
+ * The logger functions used by this player instance, configured on player instantiation.
19991
+ */
19992
+ this.logger = void 0;
19768
19993
  this.coreComponents = void 0;
19769
19994
  this.networkControllers = void 0;
19770
- this.started = false;
19771
19995
  this._emitter = new EventEmitter();
19772
19996
  this._autoLevelCapping = -1;
19773
19997
  this._maxHdcpLevel = null;
@@ -19784,11 +20008,11 @@ class Hls {
19784
20008
  this._media = null;
19785
20009
  this.url = null;
19786
20010
  this.triggeringException = void 0;
19787
- enableLogs(userConfig.debug || false, 'Hls instance');
19788
- const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
20011
+ const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
20012
+ const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
19789
20013
  this.userConfig = userConfig;
19790
20014
  if (config.progressive) {
19791
- enableStreamingMode(config);
20015
+ enableStreamingMode(config, logger);
19792
20016
  }
19793
20017
 
19794
20018
  // core controllers and network loaders
@@ -19887,7 +20111,7 @@ class Hls {
19887
20111
  try {
19888
20112
  return this.emit(event, event, eventObject);
19889
20113
  } catch (error) {
19890
- logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
20114
+ this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
19891
20115
  // Prevent recursion in error event handlers that throw #5497
19892
20116
  if (!this.triggeringException) {
19893
20117
  this.triggeringException = true;
@@ -19913,7 +20137,7 @@ class Hls {
19913
20137
  * Dispose of the instance
19914
20138
  */
19915
20139
  destroy() {
19916
- logger.log('destroy');
20140
+ this.logger.log('destroy');
19917
20141
  this.trigger(Events.DESTROYING, undefined);
19918
20142
  this.detachMedia();
19919
20143
  this.removeAllListeners();
@@ -19934,7 +20158,7 @@ class Hls {
19934
20158
  * Attaches Hls.js to a media element
19935
20159
  */
19936
20160
  attachMedia(media) {
19937
- logger.log('attachMedia');
20161
+ this.logger.log('attachMedia');
19938
20162
  this._media = media;
19939
20163
  this.trigger(Events.MEDIA_ATTACHING, {
19940
20164
  media: media
@@ -19945,7 +20169,7 @@ class Hls {
19945
20169
  * Detach Hls.js from the media
19946
20170
  */
19947
20171
  detachMedia() {
19948
- logger.log('detachMedia');
20172
+ this.logger.log('detachMedia');
19949
20173
  this.trigger(Events.MEDIA_DETACHING, undefined);
19950
20174
  this._media = null;
19951
20175
  }
@@ -19962,7 +20186,7 @@ class Hls {
19962
20186
  });
19963
20187
  this._autoLevelCapping = -1;
19964
20188
  this._maxHdcpLevel = null;
19965
- logger.log(`loadSource:${loadingSource}`);
20189
+ this.logger.log(`loadSource:${loadingSource}`);
19966
20190
  if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
19967
20191
  this.detachMedia();
19968
20192
  this.attachMedia(media);
@@ -19981,8 +20205,7 @@ class Hls {
19981
20205
  * Defaults to -1 (None: starts from earliest point)
19982
20206
  */
19983
20207
  startLoad(startPosition = -1) {
19984
- logger.log(`startLoad(${startPosition})`);
19985
- this.started = true;
20208
+ this.logger.log(`startLoad(${startPosition})`);
19986
20209
  this.networkControllers.forEach(controller => {
19987
20210
  controller.startLoad(startPosition);
19988
20211
  });
@@ -19992,34 +20215,31 @@ class Hls {
19992
20215
  * Stop loading of any stream data.
19993
20216
  */
19994
20217
  stopLoad() {
19995
- logger.log('stopLoad');
19996
- this.started = false;
20218
+ this.logger.log('stopLoad');
19997
20219
  this.networkControllers.forEach(controller => {
19998
20220
  controller.stopLoad();
19999
20221
  });
20000
20222
  }
20001
20223
 
20002
20224
  /**
20003
- * Resumes stream controller segment loading if previously started.
20225
+ * Resumes stream controller segment loading after `pauseBuffering` has been called.
20004
20226
  */
20005
20227
  resumeBuffering() {
20006
- if (this.started) {
20007
- this.networkControllers.forEach(controller => {
20008
- if ('fragmentLoader' in controller) {
20009
- controller.startLoad(-1);
20010
- }
20011
- });
20012
- }
20228
+ this.networkControllers.forEach(controller => {
20229
+ if (controller.resumeBuffering) {
20230
+ controller.resumeBuffering();
20231
+ }
20232
+ });
20013
20233
  }
20014
20234
 
20015
20235
  /**
20016
- * Stops stream controller segment loading without changing 'started' state like stopLoad().
20236
+ * Prevents stream controller from loading new segments until `resumeBuffering` is called.
20017
20237
  * This allows for media buffering to be paused without interupting playlist loading.
20018
20238
  */
20019
20239
  pauseBuffering() {
20020
20240
  this.networkControllers.forEach(controller => {
20021
- if ('fragmentLoader' in controller) {
20022
- controller.stopLoad();
20241
+ if (controller.pauseBuffering) {
20242
+ controller.pauseBuffering();
20023
20243
  }
20024
20244
  });
20025
20245
  }
@@ -20028,7 +20248,7 @@ class Hls {
20028
20248
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
20029
20249
  */
20030
20250
  swapAudioCodec() {
20031
- logger.log('swapAudioCodec');
20251
+ this.logger.log('swapAudioCodec');
20032
20252
  this.streamController.swapAudioCodec();
20033
20253
  }
20034
20254
 
@@ -20039,7 +20259,7 @@ class Hls {
20039
20259
  * Automatic recovery of media-errors by this process is configurable.
20040
20260
  */
20041
20261
  recoverMediaError() {
20042
- logger.log('recoverMediaError');
20262
+ this.logger.log('recoverMediaError');
20043
20263
  const media = this._media;
20044
20264
  this.detachMedia();
20045
20265
  if (media) {
@@ -20069,7 +20289,7 @@ class Hls {
20069
20289
  * 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.
20070
20290
  */
20071
20291
  set currentLevel(newLevel) {
20072
- logger.log(`set currentLevel:${newLevel}`);
20292
+ this.logger.log(`set currentLevel:${newLevel}`);
20073
20293
  this.levelController.manualLevel = newLevel;
20074
20294
  this.streamController.immediateLevelSwitch();
20075
20295
  }
@@ -20088,7 +20308,7 @@ class Hls {
20088
20308
  * @param newLevel - Pass -1 for automatic level selection
20089
20309
  */
20090
20310
  set nextLevel(newLevel) {
20091
- logger.log(`set nextLevel:${newLevel}`);
20311
+ this.logger.log(`set nextLevel:${newLevel}`);
20092
20312
  this.levelController.manualLevel = newLevel;
20093
20313
  this.streamController.nextLevelSwitch();
20094
20314
  }
@@ -20107,7 +20327,7 @@ class Hls {
20107
20327
  * @param newLevel - Pass -1 for automatic level selection
20108
20328
  */
20109
20329
  set loadLevel(newLevel) {
20110
- logger.log(`set loadLevel:${newLevel}`);
20330
+ this.logger.log(`set loadLevel:${newLevel}`);
20111
20331
  this.levelController.manualLevel = newLevel;
20112
20332
  }
20113
20333
 
@@ -20138,7 +20358,7 @@ class Hls {
20138
20358
  * Sets "first-level", see getter.
20139
20359
  */
20140
20360
  set firstLevel(newLevel) {
20141
- logger.log(`set firstLevel:${newLevel}`);
20361
+ this.logger.log(`set firstLevel:${newLevel}`);
20142
20362
  this.levelController.firstLevel = newLevel;
20143
20363
  }
20144
20364
 
@@ -20163,7 +20383,7 @@ class Hls {
20163
20383
  * (determined from download of first segment)
20164
20384
  */
20165
20385
  set startLevel(newLevel) {
20166
- logger.log(`set startLevel:${newLevel}`);
20386
+ this.logger.log(`set startLevel:${newLevel}`);
20167
20387
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
20168
20388
  if (newLevel !== -1) {
20169
20389
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -20238,7 +20458,7 @@ class Hls {
20238
20458
  */
20239
20459
  set autoLevelCapping(newLevel) {
20240
20460
  if (this._autoLevelCapping !== newLevel) {
20241
- logger.log(`set autoLevelCapping:${newLevel}`);
20461
+ this.logger.log(`set autoLevelCapping:${newLevel}`);
20242
20462
  this._autoLevelCapping = newLevel;
20243
20463
  this.levelController.checkMaxAutoUpdated();
20244
20464
  }
@@ -20517,5 +20737,5 @@ var KeySystemFormats = empty.KeySystemFormats;
20517
20737
  var KeySystems = empty.KeySystems;
20518
20738
  var SubtitleStreamController = empty.SubtitleStreamController;
20519
20739
  var TimelineController = empty.TimelineController;
20520
- export { AbrController, AttrList, Cues as AudioStreamController, Cues as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, Cues as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, Cues as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, Cues as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported };
20740
+ export { AbrController, AttrList, HevcVideoParser as AudioStreamController, HevcVideoParser as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, HevcVideoParser as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, HevcVideoParser as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, HevcVideoParser as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported };
20521
20741
  //# sourceMappingURL=hls.light.mjs.map