hls.js 1.5.6 → 1.5.7-0.canary.10014

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 (68) 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 +2074 -1165
  5. package/dist/hls.js.d.ts +65 -50
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1147 -858
  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 +983 -695
  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 +1756 -862
  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 +21 -21
  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 +20 -8
  25. package/src/controller/base-stream-controller.ts +149 -33
  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 +27 -6
  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 +12 -18
  36. package/src/controller/stream-controller.ts +24 -31
  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/loader/playlist-loader.ts +4 -5
  60. package/src/remux/mp4-generator.ts +196 -1
  61. package/src/remux/mp4-remuxer.ts +23 -7
  62. package/src/task-loop.ts +5 -2
  63. package/src/types/component-api.ts +2 -0
  64. package/src/types/demuxer.ts +3 -0
  65. package/src/types/events.ts +4 -0
  66. package/src/utils/codecs.ts +33 -4
  67. package/src/utils/encryption-methods-util.ts +21 -0
  68. package/src/utils/logger.ts +54 -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,58 +370,6 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
369
370
  return ErrorDetails;
370
371
  }({});
371
372
 
372
- const noop = function noop() {};
373
- const fakeLogger = {
374
- trace: noop,
375
- debug: noop,
376
- log: noop,
377
- warn: noop,
378
- info: noop,
379
- error: noop
380
- };
381
- let exportedLogger = fakeLogger;
382
-
383
- // let lastCallTime;
384
- // function formatMsgWithTimeInfo(type, msg) {
385
- // const now = Date.now();
386
- // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
387
- // lastCallTime = now;
388
- // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
389
- // return msg;
390
- // }
391
-
392
- function consolePrintFn(type) {
393
- const func = self.console[type];
394
- if (func) {
395
- return func.bind(self.console, `[${type}] >`);
396
- }
397
- return noop;
398
- }
399
- function exportLoggerFunctions(debugConfig, ...functions) {
400
- functions.forEach(function (type) {
401
- exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
402
- });
403
- }
404
- function enableLogs(debugConfig, id) {
405
- // check that console is available
406
- if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
407
- exportLoggerFunctions(debugConfig,
408
- // Remove out from list here to hard-disable a log-level
409
- // 'trace',
410
- 'debug', 'log', 'info', 'warn', 'error');
411
- // Some browsers don't allow to use bind on console object anyway
412
- // fallback to default if needed
413
- try {
414
- exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.6"}`);
415
- } catch (e) {
416
- exportedLogger = fakeLogger;
417
- }
418
- } else {
419
- exportedLogger = fakeLogger;
420
- }
421
- }
422
- const logger = exportedLogger;
423
-
424
373
  const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
425
374
  const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
426
375
 
@@ -502,6 +451,79 @@ class AttrList {
502
451
  }
503
452
  }
504
453
 
454
+ class Logger {
455
+ constructor(label, logger) {
456
+ this.trace = void 0;
457
+ this.debug = void 0;
458
+ this.log = void 0;
459
+ this.warn = void 0;
460
+ this.info = void 0;
461
+ this.error = void 0;
462
+ const lb = `[${label}]:`;
463
+ this.trace = noop;
464
+ this.debug = logger.debug.bind(null, lb);
465
+ this.log = logger.log.bind(null, lb);
466
+ this.warn = logger.warn.bind(null, lb);
467
+ this.info = logger.info.bind(null, lb);
468
+ this.error = logger.error.bind(null, lb);
469
+ }
470
+ }
471
+ const noop = function noop() {};
472
+ const fakeLogger = {
473
+ trace: noop,
474
+ debug: noop,
475
+ log: noop,
476
+ warn: noop,
477
+ info: noop,
478
+ error: noop
479
+ };
480
+ function createLogger() {
481
+ return _extends({}, fakeLogger);
482
+ }
483
+
484
+ // let lastCallTime;
485
+ // function formatMsgWithTimeInfo(type, msg) {
486
+ // const now = Date.now();
487
+ // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
488
+ // lastCallTime = now;
489
+ // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
490
+ // return msg;
491
+ // }
492
+
493
+ function consolePrintFn(type, id) {
494
+ const func = self.console[type];
495
+ return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
496
+ }
497
+ function getLoggerFn(key, debugConfig, id) {
498
+ return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
499
+ }
500
+ const exportedLogger = createLogger();
501
+ function enableLogs(debugConfig, context, id) {
502
+ // check that console is available
503
+ const newLogger = createLogger();
504
+ if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
505
+ const keys = [
506
+ // Remove out from list here to hard-disable a log-level
507
+ // 'trace',
508
+ 'debug', 'log', 'info', 'warn', 'error'];
509
+ keys.forEach(key => {
510
+ newLogger[key] = getLoggerFn(key, debugConfig, id);
511
+ });
512
+ // Some browsers don't allow to use bind on console object anyway
513
+ // fallback to default if needed
514
+ try {
515
+ newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.7-0.canary.10014"}`);
516
+ } catch (e) {
517
+ /* log fn threw an exception. All logger methods are no-ops. */
518
+ return createLogger();
519
+ }
520
+ }
521
+ // global exported logger uses the log methods from last call to `enableLogs`
522
+ _extends(exportedLogger, newLogger);
523
+ return newLogger;
524
+ }
525
+ const logger = exportedLogger;
526
+
505
527
  // Avoid exporting const enum so that these values can be inlined
506
528
 
507
529
  function isDateRangeCueAttribute(attrName) {
@@ -991,10 +1013,30 @@ class LevelDetails {
991
1013
  }
992
1014
  }
993
1015
 
1016
+ var DecrypterAesMode = {
1017
+ cbc: 0,
1018
+ ctr: 1
1019
+ };
1020
+
1021
+ function isFullSegmentEncryption(method) {
1022
+ return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
1023
+ }
1024
+ function getAesModeFromFullSegmentMethod(method) {
1025
+ switch (method) {
1026
+ case 'AES-128':
1027
+ case 'AES-256':
1028
+ return DecrypterAesMode.cbc;
1029
+ case 'AES-256-CTR':
1030
+ return DecrypterAesMode.ctr;
1031
+ default:
1032
+ throw new Error(`invalid full segment method ${method}`);
1033
+ }
1034
+ }
1035
+
994
1036
  // This file is inserted as a shim for modules which we do not want to include into the distro.
995
1037
  // This replacement is done in the "alias" plugin of the rollup config.
996
1038
  var empty = undefined;
997
- var Cues = /*@__PURE__*/getDefaultExportFromCjs(empty);
1039
+ var HevcVideoParser = /*@__PURE__*/getDefaultExportFromCjs(empty);
998
1040
 
999
1041
  function sliceUint8(array, start, end) {
1000
1042
  // @ts-expect-error This polyfills IE11 usage of Uint8Array slice.
@@ -2433,12 +2475,12 @@ class LevelKey {
2433
2475
  this.keyFormatVersions = formatversions;
2434
2476
  this.iv = iv;
2435
2477
  this.encrypted = method ? method !== 'NONE' : false;
2436
- this.isCommonEncryption = this.encrypted && method !== 'AES-128';
2478
+ this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
2437
2479
  }
2438
2480
  isSupported() {
2439
2481
  // If it's Segment encryption or No encryption, just select that key system
2440
2482
  if (this.method) {
2441
- if (this.method === 'AES-128' || this.method === 'NONE') {
2483
+ if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
2442
2484
  return true;
2443
2485
  }
2444
2486
  if (this.keyFormat === 'identity') {
@@ -2452,14 +2494,13 @@ class LevelKey {
2452
2494
  if (!this.encrypted || !this.uri) {
2453
2495
  return null;
2454
2496
  }
2455
- if (this.method === 'AES-128' && this.uri && !this.iv) {
2497
+ if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
2456
2498
  if (typeof sn !== 'number') {
2457
2499
  // We are fetching decryption data for a initialization segment
2458
- // If the segment was encrypted with AES-128
2500
+ // If the segment was encrypted with AES-128/256
2459
2501
  // It must have an IV defined. We cannot substitute the Segment Number in.
2460
- if (this.method === 'AES-128' && !this.iv) {
2461
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2462
- }
2502
+ logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2503
+
2463
2504
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
2464
2505
  sn = 0;
2465
2506
  }
@@ -2606,23 +2647,28 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
2606
2647
  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
2607
2648
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
2608
2649
  }
2609
-
2610
- // Idealy fLaC and Opus would be first (spec-compliant) but
2611
- // some browsers will report that fLaC is supported then fail.
2612
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
2613
2650
  const codecsToCheck = {
2651
+ // Idealy fLaC and Opus would be first (spec-compliant) but
2652
+ // some browsers will report that fLaC is supported then fail.
2653
+ // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
2614
2654
  flac: ['flac', 'fLaC', 'FLAC'],
2615
- opus: ['opus', 'Opus']
2655
+ opus: ['opus', 'Opus'],
2656
+ // Replace audio codec info if browser does not support mp4a.40.34,
2657
+ // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
2658
+ 'mp4a.40.34': ['mp3']
2616
2659
  }[lowerCaseCodec];
2617
2660
  for (let i = 0; i < codecsToCheck.length; i++) {
2661
+ var _getMediaSource;
2618
2662
  if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
2619
2663
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
2620
2664
  return codecsToCheck[i];
2665
+ } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
2666
+ return '';
2621
2667
  }
2622
2668
  }
2623
2669
  return lowerCaseCodec;
2624
2670
  }
2625
- const AUDIO_CODEC_REGEXP = /flac|opus/i;
2671
+ const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
2626
2672
  function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
2627
2673
  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
2628
2674
  }
@@ -2645,6 +2691,16 @@ function convertAVC1ToAVCOTI(codec) {
2645
2691
  }
2646
2692
  return codec;
2647
2693
  }
2694
+ function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
2695
+ const MediaSource = getMediaSource(preferManagedMediaSource) || {
2696
+ isTypeSupported: () => false
2697
+ };
2698
+ return {
2699
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
2700
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
2701
+ ac3: false
2702
+ };
2703
+ }
2648
2704
 
2649
2705
  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;
2650
2706
  const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -3445,10 +3501,10 @@ class PlaylistLoader {
3445
3501
  const loaderContext = loader.context;
3446
3502
  if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
3447
3503
  // same URL can't overlap
3448
- logger.trace('[playlist-loader]: playlist request ongoing');
3504
+ this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
3449
3505
  return;
3450
3506
  }
3451
- logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3507
+ this.hls.logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3452
3508
  loader.abort();
3453
3509
  }
3454
3510
 
@@ -3558,7 +3614,7 @@ class PlaylistLoader {
3558
3614
  // alt audio rendition in which quality levels (main)
3559
3615
  // contains both audio+video. but with mixed audio track not signaled
3560
3616
  if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
3561
- logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
3617
+ this.hls.logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
3562
3618
  audioTracks.unshift({
3563
3619
  type: 'main',
3564
3620
  name: 'main',
@@ -3657,7 +3713,7 @@ class PlaylistLoader {
3657
3713
  message += ` id: ${context.id} group-id: "${context.groupId}"`;
3658
3714
  }
3659
3715
  const error = new Error(message);
3660
- logger.warn(`[playlist-loader]: ${message}`);
3716
+ this.hls.logger.warn(`[playlist-loader]: ${message}`);
3661
3717
  let details = ErrorDetails.UNKNOWN;
3662
3718
  let fatal = false;
3663
3719
  const loader = this.getInternalLoader(context);
@@ -4222,7 +4278,47 @@ class LatencyController {
4222
4278
  this.currentTime = 0;
4223
4279
  this.stallCount = 0;
4224
4280
  this._latency = null;
4225
- this.timeupdateHandler = () => this.timeupdate();
4281
+ this.onTimeupdate = () => {
4282
+ const {
4283
+ media,
4284
+ levelDetails
4285
+ } = this;
4286
+ if (!media || !levelDetails) {
4287
+ return;
4288
+ }
4289
+ this.currentTime = media.currentTime;
4290
+ const latency = this.computeLatency();
4291
+ if (latency === null) {
4292
+ return;
4293
+ }
4294
+ this._latency = latency;
4295
+
4296
+ // Adapt playbackRate to meet target latency in low-latency mode
4297
+ const {
4298
+ lowLatencyMode,
4299
+ maxLiveSyncPlaybackRate
4300
+ } = this.config;
4301
+ if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4302
+ return;
4303
+ }
4304
+ const targetLatency = this.targetLatency;
4305
+ if (targetLatency === null) {
4306
+ return;
4307
+ }
4308
+ const distanceFromTarget = latency - targetLatency;
4309
+ // Only adjust playbackRate when within one target duration of targetLatency
4310
+ // and more than one second from under-buffering.
4311
+ // Playback further than one target duration from target can be considered DVR playback.
4312
+ const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4313
+ const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4314
+ if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4315
+ const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4316
+ const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4317
+ media.playbackRate = Math.min(max, Math.max(1, rate));
4318
+ } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4319
+ media.playbackRate = 1;
4320
+ }
4321
+ };
4226
4322
  this.hls = hls;
4227
4323
  this.config = hls.config;
4228
4324
  this.registerListeners();
@@ -4314,7 +4410,7 @@ class LatencyController {
4314
4410
  this.onMediaDetaching();
4315
4411
  this.levelDetails = null;
4316
4412
  // @ts-ignore
4317
- this.hls = this.timeupdateHandler = null;
4413
+ this.hls = null;
4318
4414
  }
4319
4415
  registerListeners() {
4320
4416
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -4332,11 +4428,11 @@ class LatencyController {
4332
4428
  }
4333
4429
  onMediaAttached(event, data) {
4334
4430
  this.media = data.media;
4335
- this.media.addEventListener('timeupdate', this.timeupdateHandler);
4431
+ this.media.addEventListener('timeupdate', this.onTimeupdate);
4336
4432
  }
4337
4433
  onMediaDetaching() {
4338
4434
  if (this.media) {
4339
- this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4435
+ this.media.removeEventListener('timeupdate', this.onTimeupdate);
4340
4436
  this.media = null;
4341
4437
  }
4342
4438
  }
@@ -4350,10 +4446,10 @@ class LatencyController {
4350
4446
  }) {
4351
4447
  this.levelDetails = details;
4352
4448
  if (details.advanced) {
4353
- this.timeupdate();
4449
+ this.onTimeupdate();
4354
4450
  }
4355
4451
  if (!details.live && this.media) {
4356
- this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4452
+ this.media.removeEventListener('timeupdate', this.onTimeupdate);
4357
4453
  }
4358
4454
  }
4359
4455
  onError(event, data) {
@@ -4363,48 +4459,7 @@ class LatencyController {
4363
4459
  }
4364
4460
  this.stallCount++;
4365
4461
  if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
4366
- logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
4367
- }
4368
- }
4369
- timeupdate() {
4370
- const {
4371
- media,
4372
- levelDetails
4373
- } = this;
4374
- if (!media || !levelDetails) {
4375
- return;
4376
- }
4377
- this.currentTime = media.currentTime;
4378
- const latency = this.computeLatency();
4379
- if (latency === null) {
4380
- return;
4381
- }
4382
- this._latency = latency;
4383
-
4384
- // Adapt playbackRate to meet target latency in low-latency mode
4385
- const {
4386
- lowLatencyMode,
4387
- maxLiveSyncPlaybackRate
4388
- } = this.config;
4389
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4390
- return;
4391
- }
4392
- const targetLatency = this.targetLatency;
4393
- if (targetLatency === null) {
4394
- return;
4395
- }
4396
- const distanceFromTarget = latency - targetLatency;
4397
- // Only adjust playbackRate when within one target duration of targetLatency
4398
- // and more than one second from under-buffering.
4399
- // Playback further than one target duration from target can be considered DVR playback.
4400
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4401
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4402
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4403
- const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4404
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4405
- media.playbackRate = Math.min(max, Math.max(1, rate));
4406
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4407
- media.playbackRate = 1;
4462
+ this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
4408
4463
  }
4409
4464
  }
4410
4465
  estimateLiveEdge() {
@@ -5176,18 +5231,13 @@ var ErrorActionFlags = {
5176
5231
  MoveAllAlternatesMatchingHDCP: 2,
5177
5232
  SwitchToSDR: 4
5178
5233
  }; // Reserved for future use
5179
- class ErrorController {
5234
+ class ErrorController extends Logger {
5180
5235
  constructor(hls) {
5236
+ super('error-controller', hls.logger);
5181
5237
  this.hls = void 0;
5182
5238
  this.playlistError = 0;
5183
5239
  this.penalizedRenditions = {};
5184
- this.log = void 0;
5185
- this.warn = void 0;
5186
- this.error = void 0;
5187
5240
  this.hls = hls;
5188
- this.log = logger.log.bind(logger, `[info]:`);
5189
- this.warn = logger.warn.bind(logger, `[warning]:`);
5190
- this.error = logger.error.bind(logger, `[error]:`);
5191
5241
  this.registerListeners();
5192
5242
  }
5193
5243
  registerListeners() {
@@ -5539,16 +5589,13 @@ class ErrorController {
5539
5589
  }
5540
5590
  }
5541
5591
 
5542
- class BasePlaylistController {
5592
+ class BasePlaylistController extends Logger {
5543
5593
  constructor(hls, logPrefix) {
5594
+ super(logPrefix, hls.logger);
5544
5595
  this.hls = void 0;
5545
5596
  this.timer = -1;
5546
5597
  this.requestScheduled = -1;
5547
5598
  this.canLoad = false;
5548
- this.log = void 0;
5549
- this.warn = void 0;
5550
- this.log = logger.log.bind(logger, `${logPrefix}:`);
5551
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
5552
5599
  this.hls = hls;
5553
5600
  }
5554
5601
  destroy() {
@@ -5581,7 +5628,7 @@ class BasePlaylistController {
5581
5628
  try {
5582
5629
  uri = new self.URL(attr.URI, previous.url).href;
5583
5630
  } catch (error) {
5584
- logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
5631
+ this.warn(`Could not construct new URL for Rendition Report: ${error}`);
5585
5632
  uri = attr.URI || '';
5586
5633
  }
5587
5634
  // Use exact match. Otherwise, the last partial match, if any, will be used
@@ -5668,7 +5715,12 @@ class BasePlaylistController {
5668
5715
  const cdnAge = lastAdvanced + details.ageHeader;
5669
5716
  let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
5670
5717
  if (currentGoal > 0) {
5671
- if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
5718
+ if (cdnAge > details.targetduration * 3) {
5719
+ // Omit segment and part directives when the last response was more than 3 target durations ago,
5720
+ this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
5721
+ msn = undefined;
5722
+ part = undefined;
5723
+ } else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
5672
5724
  // If we attempted to get the next or latest playlist update, but currentGoal increased,
5673
5725
  // then we either can't catchup, or the "age" header cannot be trusted.
5674
5726
  this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
@@ -6127,8 +6179,9 @@ function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
6127
6179
  }, {});
6128
6180
  }
6129
6181
 
6130
- class AbrController {
6182
+ class AbrController extends Logger {
6131
6183
  constructor(_hls) {
6184
+ super('abr', _hls.logger);
6132
6185
  this.hls = void 0;
6133
6186
  this.lastLevelLoadSec = 0;
6134
6187
  this.lastLoadedFragLevel = -1;
@@ -6242,7 +6295,7 @@ class AbrController {
6242
6295
  this.resetEstimator(nextLoadLevelBitrate);
6243
6296
  }
6244
6297
  this.clearTimer();
6245
- logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6298
+ this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6246
6299
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
6247
6300
  Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
6248
6301
  Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
@@ -6262,7 +6315,7 @@ class AbrController {
6262
6315
  }
6263
6316
  resetEstimator(abrEwmaDefaultEstimate) {
6264
6317
  if (abrEwmaDefaultEstimate) {
6265
- logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6318
+ this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6266
6319
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
6267
6320
  }
6268
6321
  this.firstSelection = -1;
@@ -6494,7 +6547,7 @@ class AbrController {
6494
6547
  }
6495
6548
  const firstLevel = this.hls.firstLevel;
6496
6549
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
6497
- logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
6550
+ this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
6498
6551
  return clamped;
6499
6552
  }
6500
6553
  get forcedAutoLevel() {
@@ -6572,13 +6625,13 @@ class AbrController {
6572
6625
  // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
6573
6626
  const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
6574
6627
  maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
6575
- logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
6628
+ this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
6576
6629
  // don't use conservative factor on bitrate test
6577
6630
  bwFactor = bwUpFactor = 1;
6578
6631
  }
6579
6632
  }
6580
6633
  const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
6581
- logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
6634
+ this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
6582
6635
  if (bestLevel > -1) {
6583
6636
  return bestLevel;
6584
6637
  }
@@ -6652,7 +6705,7 @@ class AbrController {
6652
6705
  currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
6653
6706
  currentFrameRate = minFramerate;
6654
6707
  currentBw = Math.max(currentBw, minBitrate);
6655
- logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
6708
+ this.log(`picked start tier ${JSON.stringify(startTier)}`);
6656
6709
  } else {
6657
6710
  currentCodecSet = level == null ? void 0 : level.codecSet;
6658
6711
  currentVideoRange = level == null ? void 0 : level.videoRange;
@@ -6705,9 +6758,9 @@ class AbrController {
6705
6758
  const forcedAutoLevel = this.forcedAutoLevel;
6706
6759
  if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
6707
6760
  if (levelsSkipped.length) {
6708
- 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}`);
6761
+ 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}`);
6709
6762
  }
6710
- 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}`);
6763
+ 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}`);
6711
6764
  }
6712
6765
  if (firstSelection) {
6713
6766
  this.firstSelection = i;
@@ -6943,8 +6996,9 @@ class BufferOperationQueue {
6943
6996
  }
6944
6997
 
6945
6998
  const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
6946
- class BufferController {
6999
+ class BufferController extends Logger {
6947
7000
  constructor(hls) {
7001
+ super('buffer-controller', hls.logger);
6948
7002
  // The level details used to determine duration, target-duration and live
6949
7003
  this.details = null;
6950
7004
  // cache the self generated object url to detect hijack of video tag
@@ -6974,9 +7028,6 @@ class BufferController {
6974
7028
  this.tracks = {};
6975
7029
  this.pendingTracks = {};
6976
7030
  this.sourceBuffer = void 0;
6977
- this.log = void 0;
6978
- this.warn = void 0;
6979
- this.error = void 0;
6980
7031
  this._onEndStreaming = event => {
6981
7032
  if (!this.hls) {
6982
7033
  return;
@@ -7022,15 +7073,11 @@ class BufferController {
7022
7073
  _objectUrl
7023
7074
  } = this;
7024
7075
  if (mediaSrc !== _objectUrl) {
7025
- logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
7076
+ this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
7026
7077
  }
7027
7078
  };
7028
7079
  this.hls = hls;
7029
- const logPrefix = '[buffer-controller]';
7030
7080
  this.appendSource = hls.config.preferManagedMediaSource;
7031
- this.log = logger.log.bind(logger, logPrefix);
7032
- this.warn = logger.warn.bind(logger, logPrefix);
7033
- this.error = logger.error.bind(logger, logPrefix);
7034
7081
  this._initSourceBuffer();
7035
7082
  this.registerListeners();
7036
7083
  }
@@ -7043,6 +7090,12 @@ class BufferController {
7043
7090
  this.lastMpegAudioChunk = null;
7044
7091
  // @ts-ignore
7045
7092
  this.hls = null;
7093
+ // @ts-ignore
7094
+ this._onMediaSourceOpen = this._onMediaSourceClose = null;
7095
+ // @ts-ignore
7096
+ this._onMediaSourceEnded = null;
7097
+ // @ts-ignore
7098
+ this._onStartStreaming = this._onEndStreaming = null;
7046
7099
  }
7047
7100
  registerListeners() {
7048
7101
  const {
@@ -7205,6 +7258,7 @@ class BufferController {
7205
7258
  this.resetBuffer(type);
7206
7259
  });
7207
7260
  this._initSourceBuffer();
7261
+ this.hls.resumeBuffering();
7208
7262
  }
7209
7263
  resetBuffer(type) {
7210
7264
  const sb = this.sourceBuffer[type];
@@ -8042,7 +8096,7 @@ class CapLevelController {
8042
8096
  const hls = this.hls;
8043
8097
  const maxLevel = this.getMaxLevel(levels.length - 1);
8044
8098
  if (maxLevel !== this.autoLevelCapping) {
8045
- logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
8099
+ hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
8046
8100
  }
8047
8101
  hls.autoLevelCapping = maxLevel;
8048
8102
  if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
@@ -8220,10 +8274,10 @@ class FPSController {
8220
8274
  totalDroppedFrames: droppedFrames
8221
8275
  });
8222
8276
  if (droppedFPS > 0) {
8223
- // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
8277
+ // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
8224
8278
  if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
8225
8279
  let currentLevel = hls.currentLevel;
8226
- logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
8280
+ hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
8227
8281
  if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
8228
8282
  currentLevel = currentLevel - 1;
8229
8283
  hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
@@ -8256,10 +8310,10 @@ class FPSController {
8256
8310
  }
8257
8311
 
8258
8312
  const PATHWAY_PENALTY_DURATION_MS = 300000;
8259
- class ContentSteeringController {
8313
+ class ContentSteeringController extends Logger {
8260
8314
  constructor(hls) {
8315
+ super('content-steering', hls.logger);
8261
8316
  this.hls = void 0;
8262
- this.log = void 0;
8263
8317
  this.loader = null;
8264
8318
  this.uri = null;
8265
8319
  this.pathwayId = '.';
@@ -8274,7 +8328,6 @@ class ContentSteeringController {
8274
8328
  this.subtitleTracks = null;
8275
8329
  this.penalizedPathways = {};
8276
8330
  this.hls = hls;
8277
- this.log = logger.log.bind(logger, `[content-steering]:`);
8278
8331
  this.registerListeners();
8279
8332
  }
8280
8333
  registerListeners() {
@@ -8398,7 +8451,7 @@ class ContentSteeringController {
8398
8451
  errorAction.resolved = this.pathwayId !== errorPathway;
8399
8452
  }
8400
8453
  if (!errorAction.resolved) {
8401
- 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)}`);
8454
+ 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)}`);
8402
8455
  }
8403
8456
  }
8404
8457
  }
@@ -8569,7 +8622,7 @@ class ContentSteeringController {
8569
8622
  onSuccess: (response, stats, context, networkDetails) => {
8570
8623
  this.log(`Loaded steering manifest: "${url}"`);
8571
8624
  const steeringData = response.data;
8572
- if (steeringData.VERSION !== 1) {
8625
+ if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
8573
8626
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
8574
8627
  return;
8575
8628
  }
@@ -9477,7 +9530,7 @@ const hlsDefaultConfig = _objectSpread2(_objectSpread2({
9477
9530
  });
9478
9531
  function timelineConfig() {
9479
9532
  return {
9480
- cueHandler: Cues,
9533
+ cueHandler: HevcVideoParser,
9481
9534
  // used by timeline-controller
9482
9535
  enableWebVTT: false,
9483
9536
  // used by timeline-controller
@@ -9508,7 +9561,7 @@ function timelineConfig() {
9508
9561
  /**
9509
9562
  * @ignore
9510
9563
  */
9511
- function mergeConfig(defaultConfig, userConfig) {
9564
+ function mergeConfig(defaultConfig, userConfig, logger) {
9512
9565
  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
9513
9566
  throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
9514
9567
  }
@@ -9578,7 +9631,7 @@ function deepCpy(obj) {
9578
9631
  /**
9579
9632
  * @ignore
9580
9633
  */
9581
- function enableStreamingMode(config) {
9634
+ function enableStreamingMode(config, logger) {
9582
9635
  const currentLoader = config.loader;
9583
9636
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
9584
9637
  // If a developer has configured their own loader, respect that choice
@@ -9595,10 +9648,9 @@ function enableStreamingMode(config) {
9595
9648
  }
9596
9649
  }
9597
9650
 
9598
- let chromeOrFirefox;
9599
9651
  class LevelController extends BasePlaylistController {
9600
9652
  constructor(hls, contentSteeringController) {
9601
- super(hls, '[level-controller]');
9653
+ super(hls, 'level-controller');
9602
9654
  this._levels = [];
9603
9655
  this._firstLevel = -1;
9604
9656
  this._maxAutoLevel = -1;
@@ -9669,23 +9721,15 @@ class LevelController extends BasePlaylistController {
9669
9721
  let videoCodecFound = false;
9670
9722
  let audioCodecFound = false;
9671
9723
  data.levels.forEach(levelParsed => {
9672
- var _audioCodec, _videoCodec;
9724
+ var _videoCodec;
9673
9725
  const attributes = levelParsed.attrs;
9674
-
9675
- // erase audio codec info if browser does not support mp4a.40.34.
9676
- // demuxer will autodetect codec and fallback to mpeg/audio
9677
9726
  let {
9678
9727
  audioCodec,
9679
9728
  videoCodec
9680
9729
  } = levelParsed;
9681
- if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
9682
- chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
9683
- if (chromeOrFirefox) {
9684
- levelParsed.audioCodec = audioCodec = undefined;
9685
- }
9686
- }
9687
9730
  if (audioCodec) {
9688
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
9731
+ // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
9732
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
9689
9733
  }
9690
9734
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
9691
9735
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -10027,7 +10071,12 @@ class LevelController extends BasePlaylistController {
10027
10071
  if (curLevel.fragmentError === 0) {
10028
10072
  curLevel.loadError = 0;
10029
10073
  }
10030
- this.playlistLoaded(level, data, curLevel.details);
10074
+ // Ignore matching details populated by loading a Media Playlist directly
10075
+ let previousDetails = curLevel.details;
10076
+ if (previousDetails === data.details && previousDetails.advanced) {
10077
+ previousDetails = undefined;
10078
+ }
10079
+ this.playlistLoaded(level, data, previousDetails);
10031
10080
  } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
10032
10081
  // received a delta playlist update that cannot be merged
10033
10082
  details.deltaUpdateFailed = true;
@@ -10813,8 +10862,8 @@ function createLoaderContext(frag, part = null) {
10813
10862
  var _frag$decryptdata;
10814
10863
  let byteRangeStart = start;
10815
10864
  let byteRangeEnd = end;
10816
- if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
10817
- // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
10865
+ if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
10866
+ // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
10818
10867
  // has the unencrypted size specified in the range.
10819
10868
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
10820
10869
  const fragmentLen = end - start;
@@ -10847,6 +10896,9 @@ function createGapLoadError(frag, part) {
10847
10896
  (part ? part : frag).stats.aborted = true;
10848
10897
  return new LoadError(errorData);
10849
10898
  }
10899
+ function isMethodFullSegmentAesCbc(method) {
10900
+ return method === 'AES-128' || method === 'AES-256';
10901
+ }
10850
10902
  class LoadError extends Error {
10851
10903
  constructor(data) {
10852
10904
  super(data.error.message);
@@ -10992,6 +11044,8 @@ class KeyLoader {
10992
11044
  }
10993
11045
  return this.loadKeyEME(keyInfo, frag);
10994
11046
  case 'AES-128':
11047
+ case 'AES-256':
11048
+ case 'AES-256-CTR':
10995
11049
  return this.loadKeyHTTP(keyInfo, frag);
10996
11050
  default:
10997
11051
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -11127,8 +11181,9 @@ class KeyLoader {
11127
11181
  * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
11128
11182
  * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
11129
11183
  */
11130
- class TaskLoop {
11131
- constructor() {
11184
+ class TaskLoop extends Logger {
11185
+ constructor(label, logger) {
11186
+ super(label, logger);
11132
11187
  this._boundTick = void 0;
11133
11188
  this._tickTimer = null;
11134
11189
  this._tickInterval = null;
@@ -11396,33 +11451,61 @@ function alignMediaPlaylistByPDT(details, refDetails) {
11396
11451
  }
11397
11452
 
11398
11453
  class AESCrypto {
11399
- constructor(subtle, iv) {
11454
+ constructor(subtle, iv, aesMode) {
11400
11455
  this.subtle = void 0;
11401
11456
  this.aesIV = void 0;
11457
+ this.aesMode = void 0;
11402
11458
  this.subtle = subtle;
11403
11459
  this.aesIV = iv;
11460
+ this.aesMode = aesMode;
11404
11461
  }
11405
11462
  decrypt(data, key) {
11406
- return this.subtle.decrypt({
11407
- name: 'AES-CBC',
11408
- iv: this.aesIV
11409
- }, key, data);
11463
+ switch (this.aesMode) {
11464
+ case DecrypterAesMode.cbc:
11465
+ return this.subtle.decrypt({
11466
+ name: 'AES-CBC',
11467
+ iv: this.aesIV
11468
+ }, key, data);
11469
+ case DecrypterAesMode.ctr:
11470
+ return this.subtle.decrypt({
11471
+ name: 'AES-CTR',
11472
+ counter: this.aesIV,
11473
+ length: 64
11474
+ },
11475
+ //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
11476
+ key, data);
11477
+ default:
11478
+ throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
11479
+ }
11410
11480
  }
11411
11481
  }
11412
11482
 
11413
11483
  class FastAESKey {
11414
- constructor(subtle, key) {
11484
+ constructor(subtle, key, aesMode) {
11415
11485
  this.subtle = void 0;
11416
11486
  this.key = void 0;
11487
+ this.aesMode = void 0;
11417
11488
  this.subtle = subtle;
11418
11489
  this.key = key;
11490
+ this.aesMode = aesMode;
11419
11491
  }
11420
11492
  expandKey() {
11493
+ const subtleAlgoName = getSubtleAlgoName(this.aesMode);
11421
11494
  return this.subtle.importKey('raw', this.key, {
11422
- name: 'AES-CBC'
11495
+ name: subtleAlgoName
11423
11496
  }, false, ['encrypt', 'decrypt']);
11424
11497
  }
11425
11498
  }
11499
+ function getSubtleAlgoName(aesMode) {
11500
+ switch (aesMode) {
11501
+ case DecrypterAesMode.cbc:
11502
+ return 'AES-CBC';
11503
+ case DecrypterAesMode.ctr:
11504
+ return 'AES-CTR';
11505
+ default:
11506
+ throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
11507
+ }
11508
+ }
11426
11509
 
11427
11510
  // PKCS7
11428
11511
  function removePadding(array) {
@@ -11672,7 +11755,8 @@ class Decrypter {
11672
11755
  this.currentIV = null;
11673
11756
  this.currentResult = null;
11674
11757
  this.useSoftware = void 0;
11675
- this.useSoftware = config.enableSoftwareAES;
11758
+ this.enableSoftwareAES = void 0;
11759
+ this.enableSoftwareAES = config.enableSoftwareAES;
11676
11760
  this.removePKCS7Padding = removePKCS7Padding;
11677
11761
  // built in decryptor expects PKCS7 padding
11678
11762
  if (removePKCS7Padding) {
@@ -11685,9 +11769,7 @@ class Decrypter {
11685
11769
  /* no-op */
11686
11770
  }
11687
11771
  }
11688
- if (this.subtle === null) {
11689
- this.useSoftware = true;
11690
- }
11772
+ this.useSoftware = this.subtle === null;
11691
11773
  }
11692
11774
  destroy() {
11693
11775
  this.subtle = null;
@@ -11725,10 +11807,10 @@ class Decrypter {
11725
11807
  this.softwareDecrypter = null;
11726
11808
  }
11727
11809
  }
11728
- decrypt(data, key, iv) {
11810
+ decrypt(data, key, iv, aesMode) {
11729
11811
  if (this.useSoftware) {
11730
11812
  return new Promise((resolve, reject) => {
11731
- this.softwareDecrypt(new Uint8Array(data), key, iv);
11813
+ this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
11732
11814
  const decryptResult = this.flush();
11733
11815
  if (decryptResult) {
11734
11816
  resolve(decryptResult.buffer);
@@ -11737,17 +11819,21 @@ class Decrypter {
11737
11819
  }
11738
11820
  });
11739
11821
  }
11740
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
11822
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
11741
11823
  }
11742
11824
 
11743
11825
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
11744
11826
  // data is handled in the flush() call
11745
- softwareDecrypt(data, key, iv) {
11827
+ softwareDecrypt(data, key, iv, aesMode) {
11746
11828
  const {
11747
11829
  currentIV,
11748
11830
  currentResult,
11749
11831
  remainderData
11750
11832
  } = this;
11833
+ if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
11834
+ logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
11835
+ return null;
11836
+ }
11751
11837
  this.logOnce('JS AES decrypt');
11752
11838
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
11753
11839
  // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
@@ -11780,11 +11866,11 @@ class Decrypter {
11780
11866
  }
11781
11867
  return result;
11782
11868
  }
11783
- webCryptoDecrypt(data, key, iv) {
11869
+ webCryptoDecrypt(data, key, iv, aesMode) {
11784
11870
  const subtle = this.subtle;
11785
11871
  if (this.key !== key || !this.fastAesKey) {
11786
11872
  this.key = key;
11787
- this.fastAesKey = new FastAESKey(subtle, key);
11873
+ this.fastAesKey = new FastAESKey(subtle, key, aesMode);
11788
11874
  }
11789
11875
  return this.fastAesKey.expandKey().then(aesKey => {
11790
11876
  // decrypt using web crypto
@@ -11792,22 +11878,25 @@ class Decrypter {
11792
11878
  return Promise.reject(new Error('web crypto not initialized'));
11793
11879
  }
11794
11880
  this.logOnce('WebCrypto AES decrypt');
11795
- const crypto = new AESCrypto(subtle, new Uint8Array(iv));
11881
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
11796
11882
  return crypto.decrypt(data.buffer, aesKey);
11797
11883
  }).catch(err => {
11798
11884
  logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
11799
- return this.onWebCryptoError(data, key, iv);
11885
+ return this.onWebCryptoError(data, key, iv, aesMode);
11800
11886
  });
11801
11887
  }
11802
- onWebCryptoError(data, key, iv) {
11803
- this.useSoftware = true;
11804
- this.logEnabled = true;
11805
- this.softwareDecrypt(data, key, iv);
11806
- const decryptResult = this.flush();
11807
- if (decryptResult) {
11808
- return decryptResult.buffer;
11888
+ onWebCryptoError(data, key, iv, aesMode) {
11889
+ const enableSoftwareAES = this.enableSoftwareAES;
11890
+ if (enableSoftwareAES) {
11891
+ this.useSoftware = true;
11892
+ this.logEnabled = true;
11893
+ this.softwareDecrypt(data, key, iv, aesMode);
11894
+ const decryptResult = this.flush();
11895
+ if (decryptResult) {
11896
+ return decryptResult.buffer;
11897
+ }
11809
11898
  }
11810
- throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
11899
+ throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
11811
11900
  }
11812
11901
  getValidChunk(data) {
11813
11902
  let currentChunk = data;
@@ -11858,7 +11947,7 @@ const State = {
11858
11947
  };
11859
11948
  class BaseStreamController extends TaskLoop {
11860
11949
  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
11861
- super();
11950
+ super(logPrefix, hls.logger);
11862
11951
  this.hls = void 0;
11863
11952
  this.fragPrevious = null;
11864
11953
  this.fragCurrent = null;
@@ -11883,22 +11972,98 @@ class BaseStreamController extends TaskLoop {
11883
11972
  this.startFragRequested = false;
11884
11973
  this.decrypter = void 0;
11885
11974
  this.initPTS = [];
11886
- this.onvseeking = null;
11887
- this.onvended = null;
11888
- this.logPrefix = '';
11889
- this.log = void 0;
11890
- this.warn = void 0;
11975
+ this.buffering = true;
11976
+ this.loadingParts = false;
11977
+ this.onMediaSeeking = () => {
11978
+ const {
11979
+ config,
11980
+ fragCurrent,
11981
+ media,
11982
+ mediaBuffer,
11983
+ state
11984
+ } = this;
11985
+ const currentTime = media ? media.currentTime : 0;
11986
+ const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
11987
+ this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
11988
+ if (this.state === State.ENDED) {
11989
+ this.resetLoadingState();
11990
+ } else if (fragCurrent) {
11991
+ // Seeking while frag load is in progress
11992
+ const tolerance = config.maxFragLookUpTolerance;
11993
+ const fragStartOffset = fragCurrent.start - tolerance;
11994
+ const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
11995
+ // if seeking out of buffered range or into new one
11996
+ if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
11997
+ const pastFragment = currentTime > fragEndOffset;
11998
+ // if the seek position is outside the current fragment range
11999
+ if (currentTime < fragStartOffset || pastFragment) {
12000
+ if (pastFragment && fragCurrent.loader) {
12001
+ this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
12002
+ fragCurrent.abortRequests();
12003
+ this.resetLoadingState();
12004
+ }
12005
+ this.fragPrevious = null;
12006
+ }
12007
+ }
12008
+ }
12009
+ if (media) {
12010
+ // Remove gap fragments
12011
+ this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
12012
+ this.lastCurrentTime = currentTime;
12013
+ if (!this.loadingParts) {
12014
+ const bufferEnd = Math.max(bufferInfo.end, currentTime);
12015
+ const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
12016
+ if (shouldLoadParts) {
12017
+ this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
12018
+ this.loadingParts = shouldLoadParts;
12019
+ }
12020
+ }
12021
+ }
12022
+
12023
+ // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
12024
+ if (!this.loadedmetadata && !bufferInfo.len) {
12025
+ this.nextLoadPosition = this.startPosition = currentTime;
12026
+ }
12027
+
12028
+ // Async tick to speed up processing
12029
+ this.tickImmediate();
12030
+ };
12031
+ this.onMediaEnded = () => {
12032
+ // reset startPosition and lastCurrentTime to restart playback @ stream beginning
12033
+ this.startPosition = this.lastCurrentTime = 0;
12034
+ if (this.playlistType === PlaylistLevelType.MAIN) {
12035
+ this.hls.trigger(Events.MEDIA_ENDED, {
12036
+ stalled: false
12037
+ });
12038
+ }
12039
+ };
11891
12040
  this.playlistType = playlistType;
11892
- this.logPrefix = logPrefix;
11893
- this.log = logger.log.bind(logger, `${logPrefix}:`);
11894
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
11895
12041
  this.hls = hls;
11896
12042
  this.fragmentLoader = new FragmentLoader(hls.config);
11897
12043
  this.keyLoader = keyLoader;
11898
12044
  this.fragmentTracker = fragmentTracker;
11899
12045
  this.config = hls.config;
11900
12046
  this.decrypter = new Decrypter(hls.config);
12047
+ }
12048
+ registerListeners() {
12049
+ const {
12050
+ hls
12051
+ } = this;
12052
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
12053
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
12054
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
11901
12055
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
12056
+ hls.on(Events.ERROR, this.onError, this);
12057
+ }
12058
+ unregisterListeners() {
12059
+ const {
12060
+ hls
12061
+ } = this;
12062
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
12063
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
12064
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
12065
+ hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
12066
+ hls.off(Events.ERROR, this.onError, this);
11902
12067
  }
11903
12068
  doTick() {
11904
12069
  this.onTickEnd();
@@ -11922,6 +12087,12 @@ class BaseStreamController extends TaskLoop {
11922
12087
  this.clearNextTick();
11923
12088
  this.state = State.STOPPED;
11924
12089
  }
12090
+ pauseBuffering() {
12091
+ this.buffering = false;
12092
+ }
12093
+ resumeBuffering() {
12094
+ this.buffering = true;
12095
+ }
11925
12096
  _streamEnded(bufferInfo, levelDetails) {
11926
12097
  // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
11927
12098
  // of nothing loading/loaded return false
@@ -11952,10 +12123,8 @@ class BaseStreamController extends TaskLoop {
11952
12123
  }
11953
12124
  onMediaAttached(event, data) {
11954
12125
  const media = this.media = this.mediaBuffer = data.media;
11955
- this.onvseeking = this.onMediaSeeking.bind(this);
11956
- this.onvended = this.onMediaEnded.bind(this);
11957
- media.addEventListener('seeking', this.onvseeking);
11958
- media.addEventListener('ended', this.onvended);
12126
+ media.addEventListener('seeking', this.onMediaSeeking);
12127
+ media.addEventListener('ended', this.onMediaEnded);
11959
12128
  const config = this.config;
11960
12129
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
11961
12130
  this.startLoad(config.startPosition);
@@ -11969,10 +12138,9 @@ class BaseStreamController extends TaskLoop {
11969
12138
  }
11970
12139
 
11971
12140
  // remove video listeners
11972
- if (media && this.onvseeking && this.onvended) {
11973
- media.removeEventListener('seeking', this.onvseeking);
11974
- media.removeEventListener('ended', this.onvended);
11975
- this.onvseeking = this.onvended = null;
12141
+ if (media) {
12142
+ media.removeEventListener('seeking', this.onMediaSeeking);
12143
+ media.removeEventListener('ended', this.onMediaEnded);
11976
12144
  }
11977
12145
  if (this.keyLoader) {
11978
12146
  this.keyLoader.detach();
@@ -11982,56 +12150,8 @@ class BaseStreamController extends TaskLoop {
11982
12150
  this.fragmentTracker.removeAllFragments();
11983
12151
  this.stopLoad();
11984
12152
  }
11985
- onMediaSeeking() {
11986
- const {
11987
- config,
11988
- fragCurrent,
11989
- media,
11990
- mediaBuffer,
11991
- state
11992
- } = this;
11993
- const currentTime = media ? media.currentTime : 0;
11994
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
11995
- this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
11996
- if (this.state === State.ENDED) {
11997
- this.resetLoadingState();
11998
- } else if (fragCurrent) {
11999
- // Seeking while frag load is in progress
12000
- const tolerance = config.maxFragLookUpTolerance;
12001
- const fragStartOffset = fragCurrent.start - tolerance;
12002
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
12003
- // if seeking out of buffered range or into new one
12004
- if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
12005
- const pastFragment = currentTime > fragEndOffset;
12006
- // if the seek position is outside the current fragment range
12007
- if (currentTime < fragStartOffset || pastFragment) {
12008
- if (pastFragment && fragCurrent.loader) {
12009
- this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
12010
- fragCurrent.abortRequests();
12011
- this.resetLoadingState();
12012
- }
12013
- this.fragPrevious = null;
12014
- }
12015
- }
12016
- }
12017
- if (media) {
12018
- // Remove gap fragments
12019
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
12020
- this.lastCurrentTime = currentTime;
12021
- }
12022
-
12023
- // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
12024
- if (!this.loadedmetadata && !bufferInfo.len) {
12025
- this.nextLoadPosition = this.startPosition = currentTime;
12026
- }
12027
-
12028
- // Async tick to speed up processing
12029
- this.tickImmediate();
12030
- }
12031
- onMediaEnded() {
12032
- // reset startPosition and lastCurrentTime to restart playback @ stream beginning
12033
- this.startPosition = this.lastCurrentTime = 0;
12034
- }
12153
+ onManifestLoading() {}
12154
+ onError(event, data) {}
12035
12155
  onManifestLoaded(event, data) {
12036
12156
  this.startTimeOffset = data.startTimeOffset;
12037
12157
  this.initPTS = [];
@@ -12041,7 +12161,7 @@ class BaseStreamController extends TaskLoop {
12041
12161
  this.stopLoad();
12042
12162
  super.onHandlerDestroying();
12043
12163
  // @ts-ignore
12044
- this.hls = null;
12164
+ this.hls = this.onMediaSeeking = this.onMediaEnded = null;
12045
12165
  }
12046
12166
  onHandlerDestroyed() {
12047
12167
  this.state = State.STOPPED;
@@ -12172,10 +12292,10 @@ class BaseStreamController extends TaskLoop {
12172
12292
  const decryptData = frag.decryptdata;
12173
12293
 
12174
12294
  // check to see if the payload needs to be decrypted
12175
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
12295
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
12176
12296
  const startTime = self.performance.now();
12177
12297
  // decrypt init segment data
12178
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
12298
+ return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
12179
12299
  hls.trigger(Events.ERROR, {
12180
12300
  type: ErrorTypes.MEDIA_ERROR,
12181
12301
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -12287,7 +12407,7 @@ class BaseStreamController extends TaskLoop {
12287
12407
  }
12288
12408
  let keyLoadingPromise = null;
12289
12409
  if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
12290
- this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
12410
+ this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
12291
12411
  this.state = State.KEY_LOADING;
12292
12412
  this.fragCurrent = frag;
12293
12413
  keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
@@ -12308,8 +12428,16 @@ class BaseStreamController extends TaskLoop {
12308
12428
  } else if (!frag.encrypted && details.encryptedFragments.length) {
12309
12429
  this.keyLoader.loadClear(frag, details.encryptedFragments);
12310
12430
  }
12431
+ const fragPrevious = this.fragPrevious;
12432
+ if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
12433
+ const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
12434
+ if (shouldLoadParts !== this.loadingParts) {
12435
+ this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
12436
+ this.loadingParts = shouldLoadParts;
12437
+ }
12438
+ }
12311
12439
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
12312
- if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
12440
+ if (this.loadingParts && frag.sn !== 'initSegment') {
12313
12441
  const partList = details.partList;
12314
12442
  if (partList && progressCallback) {
12315
12443
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -12318,7 +12446,7 @@ class BaseStreamController extends TaskLoop {
12318
12446
  const partIndex = this.getNextPart(partList, frag, targetBufferTime);
12319
12447
  if (partIndex > -1) {
12320
12448
  const part = partList[partIndex];
12321
- 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))}`);
12449
+ 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))}`);
12322
12450
  this.nextLoadPosition = part.start + part.duration;
12323
12451
  this.state = State.FRAG_LOADING;
12324
12452
  let _result;
@@ -12347,7 +12475,14 @@ class BaseStreamController extends TaskLoop {
12347
12475
  }
12348
12476
  }
12349
12477
  }
12350
- 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))}`);
12478
+ if (frag.sn !== 'initSegment' && this.loadingParts) {
12479
+ this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
12480
+ this.loadingParts = false;
12481
+ } else if (!frag.url) {
12482
+ // Selected fragment hint for part but not loading parts
12483
+ return Promise.resolve(null);
12484
+ }
12485
+ 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))}`);
12351
12486
  // Don't update nextLoadPosition for fragments which are not buffered
12352
12487
  if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
12353
12488
  this.nextLoadPosition = frag.start + frag.duration;
@@ -12445,8 +12580,36 @@ class BaseStreamController extends TaskLoop {
12445
12580
  if (part) {
12446
12581
  part.stats.parsing.end = now;
12447
12582
  }
12583
+ // See if part loading should be disabled/enabled based on buffer and playback position.
12584
+ if (frag.sn !== 'initSegment') {
12585
+ const levelDetails = this.getLevelDetails();
12586
+ const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
12587
+ const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
12588
+ if (shouldLoadParts !== this.loadingParts) {
12589
+ this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
12590
+ this.loadingParts = shouldLoadParts;
12591
+ }
12592
+ }
12448
12593
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
12449
12594
  }
12595
+ shouldLoadParts(details, bufferEnd) {
12596
+ if (this.config.lowLatencyMode) {
12597
+ if (!details) {
12598
+ return this.loadingParts;
12599
+ }
12600
+ if (details != null && details.partList) {
12601
+ var _details$fragmentHint;
12602
+ // Buffer must be ahead of first part + duration of parts after last segment
12603
+ // and playback must be at or past segment adjacent to part list
12604
+ const firstPart = details.partList[0];
12605
+ const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
12606
+ if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
12607
+ return true;
12608
+ }
12609
+ }
12610
+ }
12611
+ return false;
12612
+ }
12450
12613
  getCurrentContext(chunkMeta) {
12451
12614
  const {
12452
12615
  levels,
@@ -12595,7 +12758,8 @@ class BaseStreamController extends TaskLoop {
12595
12758
  config
12596
12759
  } = this;
12597
12760
  const start = fragments[0].start;
12598
- let frag;
12761
+ const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
12762
+ let frag = null;
12599
12763
  if (levelDetails.live) {
12600
12764
  const initialLiveManifestSize = config.initialLiveManifestSize;
12601
12765
  if (fragLen < initialLiveManifestSize) {
@@ -12607,6 +12771,10 @@ class BaseStreamController extends TaskLoop {
12607
12771
  // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
12608
12772
  // we get the fragment matching that start time
12609
12773
  if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
12774
+ if (canLoadParts && !this.loadingParts) {
12775
+ this.log(`LL-Part loading ON for initial live fragment`);
12776
+ this.loadingParts = true;
12777
+ }
12610
12778
  frag = this.getInitialLiveFragment(levelDetails, fragments);
12611
12779
  this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
12612
12780
  }
@@ -12617,7 +12785,7 @@ class BaseStreamController extends TaskLoop {
12617
12785
 
12618
12786
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
12619
12787
  if (!frag) {
12620
- const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
12788
+ const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
12621
12789
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
12622
12790
  }
12623
12791
  return this.mapToInitFragWhenRequired(frag);
@@ -12739,7 +12907,7 @@ class BaseStreamController extends TaskLoop {
12739
12907
  } = levelDetails;
12740
12908
  const tolerance = config.maxFragLookUpTolerance;
12741
12909
  const partList = levelDetails.partList;
12742
- const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
12910
+ const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
12743
12911
  if (loadingParts && fragmentHint && !this.bitrateTest) {
12744
12912
  // Include incomplete fragment with parts at end
12745
12913
  fragments = fragments.concat(fragmentHint);
@@ -12932,7 +13100,7 @@ class BaseStreamController extends TaskLoop {
12932
13100
  errorAction.resolved = true;
12933
13101
  }
12934
13102
  } else {
12935
- logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
13103
+ this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
12936
13104
  return;
12937
13105
  }
12938
13106
  } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
@@ -13327,6 +13495,7 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
13327
13495
  */
13328
13496
  function getAudioConfig(observer, data, offset, audioCodec) {
13329
13497
  let adtsObjectType;
13498
+ let originalAdtsObjectType;
13330
13499
  let adtsExtensionSamplingIndex;
13331
13500
  let adtsChannelConfig;
13332
13501
  let config;
@@ -13334,7 +13503,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13334
13503
  const manifestCodec = audioCodec;
13335
13504
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
13336
13505
  // byte 2
13337
- adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
13506
+ adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
13338
13507
  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
13339
13508
  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
13340
13509
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
@@ -13351,8 +13520,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13351
13520
  // byte 3
13352
13521
  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
13353
13522
  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
13354
- // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
13355
- if (/firefox/i.test(userAgent)) {
13523
+ // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
13524
+ if (/firefox|palemoon/i.test(userAgent)) {
13356
13525
  if (adtsSamplingIndex >= 6) {
13357
13526
  adtsObjectType = 5;
13358
13527
  config = new Array(4);
@@ -13446,6 +13615,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13446
13615
  samplerate: adtsSamplingRates[adtsSamplingIndex],
13447
13616
  channelCount: adtsChannelConfig,
13448
13617
  codec: 'mp4a.40.' + adtsObjectType,
13618
+ parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
13449
13619
  manifestCodec
13450
13620
  };
13451
13621
  }
@@ -13500,7 +13670,8 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
13500
13670
  track.channelCount = config.channelCount;
13501
13671
  track.codec = config.codec;
13502
13672
  track.manifestCodec = config.manifestCodec;
13503
- logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
13673
+ track.parsedCodec = config.parsedCodec;
13674
+ logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
13504
13675
  }
13505
13676
  }
13506
13677
  function getFrameDuration(samplerate) {
@@ -13978,17 +14149,121 @@ class BaseVideoParser {
13978
14149
  logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
13979
14150
  }
13980
14151
  }
13981
- }
13982
-
13983
- /**
13984
- * Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
13985
- */
13986
-
13987
- class ExpGolomb {
13988
- constructor(data) {
13989
- this.data = void 0;
13990
- this.bytesAvailable = void 0;
13991
- this.word = void 0;
14152
+ parseNALu(track, array) {
14153
+ const len = array.byteLength;
14154
+ let state = track.naluState || 0;
14155
+ const lastState = state;
14156
+ const units = [];
14157
+ let i = 0;
14158
+ let value;
14159
+ let overflow;
14160
+ let unitType;
14161
+ let lastUnitStart = -1;
14162
+ let lastUnitType = 0;
14163
+ // logger.log('PES:' + Hex.hexDump(array));
14164
+
14165
+ if (state === -1) {
14166
+ // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
14167
+ lastUnitStart = 0;
14168
+ // NALu type is value read from offset 0
14169
+ lastUnitType = this.getNALuType(array, 0);
14170
+ state = 0;
14171
+ i = 1;
14172
+ }
14173
+ while (i < len) {
14174
+ value = array[i++];
14175
+ // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
14176
+ if (!state) {
14177
+ state = value ? 0 : 1;
14178
+ continue;
14179
+ }
14180
+ if (state === 1) {
14181
+ state = value ? 0 : 2;
14182
+ continue;
14183
+ }
14184
+ // here we have state either equal to 2 or 3
14185
+ if (!value) {
14186
+ state = 3;
14187
+ } else if (value === 1) {
14188
+ overflow = i - state - 1;
14189
+ if (lastUnitStart >= 0) {
14190
+ const unit = {
14191
+ data: array.subarray(lastUnitStart, overflow),
14192
+ type: lastUnitType
14193
+ };
14194
+ // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
14195
+ units.push(unit);
14196
+ } else {
14197
+ // lastUnitStart is undefined => this is the first start code found in this PES packet
14198
+ // first check if start code delimiter is overlapping between 2 PES packets,
14199
+ // ie it started in last packet (lastState not zero)
14200
+ // and ended at the beginning of this PES packet (i <= 4 - lastState)
14201
+ const lastUnit = this.getLastNalUnit(track.samples);
14202
+ if (lastUnit) {
14203
+ if (lastState && i <= 4 - lastState) {
14204
+ // start delimiter overlapping between PES packets
14205
+ // strip start delimiter bytes from the end of last NAL unit
14206
+ // check if lastUnit had a state different from zero
14207
+ if (lastUnit.state) {
14208
+ // strip last bytes
14209
+ lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
14210
+ }
14211
+ }
14212
+ // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
14213
+
14214
+ if (overflow > 0) {
14215
+ // logger.log('first NALU found with overflow:' + overflow);
14216
+ lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
14217
+ lastUnit.state = 0;
14218
+ }
14219
+ }
14220
+ }
14221
+ // check if we can read unit type
14222
+ if (i < len) {
14223
+ unitType = this.getNALuType(array, i);
14224
+ // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
14225
+ lastUnitStart = i;
14226
+ lastUnitType = unitType;
14227
+ state = 0;
14228
+ } else {
14229
+ // not enough byte to read unit type. let's read it on next PES parsing
14230
+ state = -1;
14231
+ }
14232
+ } else {
14233
+ state = 0;
14234
+ }
14235
+ }
14236
+ if (lastUnitStart >= 0 && state >= 0) {
14237
+ const unit = {
14238
+ data: array.subarray(lastUnitStart, len),
14239
+ type: lastUnitType,
14240
+ state: state
14241
+ };
14242
+ units.push(unit);
14243
+ // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
14244
+ }
14245
+ // no NALu found
14246
+ if (units.length === 0) {
14247
+ // append pes.data to previous NAL unit
14248
+ const lastUnit = this.getLastNalUnit(track.samples);
14249
+ if (lastUnit) {
14250
+ lastUnit.data = appendUint8Array(lastUnit.data, array);
14251
+ }
14252
+ }
14253
+ track.naluState = state;
14254
+ return units;
14255
+ }
14256
+ }
14257
+
14258
+ /**
14259
+ * Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
14260
+ */
14261
+
14262
+ class ExpGolomb {
14263
+ constructor(data) {
14264
+ this.data = void 0;
14265
+ this.bytesAvailable = void 0;
14266
+ this.word = void 0;
13992
14267
  this.bitsAvailable = void 0;
13993
14268
  this.data = data;
13994
14269
  // the number of bytes left to examine in this.data
@@ -14120,194 +14395,11 @@ class ExpGolomb {
14120
14395
  readUInt() {
14121
14396
  return this.readBits(32);
14122
14397
  }
14123
-
14124
- /**
14125
- * Advance the ExpGolomb decoder past a scaling list. The scaling
14126
- * list is optionally transmitted as part of a sequence parameter
14127
- * set and is not relevant to transmuxing.
14128
- * @param count the number of entries in this scaling list
14129
- * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
14130
- */
14131
- skipScalingList(count) {
14132
- let lastScale = 8;
14133
- let nextScale = 8;
14134
- let deltaScale;
14135
- for (let j = 0; j < count; j++) {
14136
- if (nextScale !== 0) {
14137
- deltaScale = this.readEG();
14138
- nextScale = (lastScale + deltaScale + 256) % 256;
14139
- }
14140
- lastScale = nextScale === 0 ? lastScale : nextScale;
14141
- }
14142
- }
14143
-
14144
- /**
14145
- * Read a sequence parameter set and return some interesting video
14146
- * properties. A sequence parameter set is the H264 metadata that
14147
- * describes the properties of upcoming video frames.
14148
- * @returns an object with configuration parsed from the
14149
- * sequence parameter set, including the dimensions of the
14150
- * associated video frames.
14151
- */
14152
- readSPS() {
14153
- let frameCropLeftOffset = 0;
14154
- let frameCropRightOffset = 0;
14155
- let frameCropTopOffset = 0;
14156
- let frameCropBottomOffset = 0;
14157
- let numRefFramesInPicOrderCntCycle;
14158
- let scalingListCount;
14159
- let i;
14160
- const readUByte = this.readUByte.bind(this);
14161
- const readBits = this.readBits.bind(this);
14162
- const readUEG = this.readUEG.bind(this);
14163
- const readBoolean = this.readBoolean.bind(this);
14164
- const skipBits = this.skipBits.bind(this);
14165
- const skipEG = this.skipEG.bind(this);
14166
- const skipUEG = this.skipUEG.bind(this);
14167
- const skipScalingList = this.skipScalingList.bind(this);
14168
- readUByte();
14169
- const profileIdc = readUByte(); // profile_idc
14170
- readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
14171
- skipBits(3); // reserved_zero_3bits u(3),
14172
- readUByte(); // level_idc u(8)
14173
- skipUEG(); // seq_parameter_set_id
14174
- // some profiles have more optional data we don't need
14175
- if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
14176
- const chromaFormatIdc = readUEG();
14177
- if (chromaFormatIdc === 3) {
14178
- skipBits(1);
14179
- } // separate_colour_plane_flag
14180
-
14181
- skipUEG(); // bit_depth_luma_minus8
14182
- skipUEG(); // bit_depth_chroma_minus8
14183
- skipBits(1); // qpprime_y_zero_transform_bypass_flag
14184
- if (readBoolean()) {
14185
- // seq_scaling_matrix_present_flag
14186
- scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
14187
- for (i = 0; i < scalingListCount; i++) {
14188
- if (readBoolean()) {
14189
- // seq_scaling_list_present_flag[ i ]
14190
- if (i < 6) {
14191
- skipScalingList(16);
14192
- } else {
14193
- skipScalingList(64);
14194
- }
14195
- }
14196
- }
14197
- }
14198
- }
14199
- skipUEG(); // log2_max_frame_num_minus4
14200
- const picOrderCntType = readUEG();
14201
- if (picOrderCntType === 0) {
14202
- readUEG(); // log2_max_pic_order_cnt_lsb_minus4
14203
- } else if (picOrderCntType === 1) {
14204
- skipBits(1); // delta_pic_order_always_zero_flag
14205
- skipEG(); // offset_for_non_ref_pic
14206
- skipEG(); // offset_for_top_to_bottom_field
14207
- numRefFramesInPicOrderCntCycle = readUEG();
14208
- for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
14209
- skipEG();
14210
- } // offset_for_ref_frame[ i ]
14211
- }
14212
- skipUEG(); // max_num_ref_frames
14213
- skipBits(1); // gaps_in_frame_num_value_allowed_flag
14214
- const picWidthInMbsMinus1 = readUEG();
14215
- const picHeightInMapUnitsMinus1 = readUEG();
14216
- const frameMbsOnlyFlag = readBits(1);
14217
- if (frameMbsOnlyFlag === 0) {
14218
- skipBits(1);
14219
- } // mb_adaptive_frame_field_flag
14220
-
14221
- skipBits(1); // direct_8x8_inference_flag
14222
- if (readBoolean()) {
14223
- // frame_cropping_flag
14224
- frameCropLeftOffset = readUEG();
14225
- frameCropRightOffset = readUEG();
14226
- frameCropTopOffset = readUEG();
14227
- frameCropBottomOffset = readUEG();
14228
- }
14229
- let pixelRatio = [1, 1];
14230
- if (readBoolean()) {
14231
- // vui_parameters_present_flag
14232
- if (readBoolean()) {
14233
- // aspect_ratio_info_present_flag
14234
- const aspectRatioIdc = readUByte();
14235
- switch (aspectRatioIdc) {
14236
- case 1:
14237
- pixelRatio = [1, 1];
14238
- break;
14239
- case 2:
14240
- pixelRatio = [12, 11];
14241
- break;
14242
- case 3:
14243
- pixelRatio = [10, 11];
14244
- break;
14245
- case 4:
14246
- pixelRatio = [16, 11];
14247
- break;
14248
- case 5:
14249
- pixelRatio = [40, 33];
14250
- break;
14251
- case 6:
14252
- pixelRatio = [24, 11];
14253
- break;
14254
- case 7:
14255
- pixelRatio = [20, 11];
14256
- break;
14257
- case 8:
14258
- pixelRatio = [32, 11];
14259
- break;
14260
- case 9:
14261
- pixelRatio = [80, 33];
14262
- break;
14263
- case 10:
14264
- pixelRatio = [18, 11];
14265
- break;
14266
- case 11:
14267
- pixelRatio = [15, 11];
14268
- break;
14269
- case 12:
14270
- pixelRatio = [64, 33];
14271
- break;
14272
- case 13:
14273
- pixelRatio = [160, 99];
14274
- break;
14275
- case 14:
14276
- pixelRatio = [4, 3];
14277
- break;
14278
- case 15:
14279
- pixelRatio = [3, 2];
14280
- break;
14281
- case 16:
14282
- pixelRatio = [2, 1];
14283
- break;
14284
- case 255:
14285
- {
14286
- pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
14287
- break;
14288
- }
14289
- }
14290
- }
14291
- }
14292
- return {
14293
- width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
14294
- height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
14295
- pixelRatio: pixelRatio
14296
- };
14297
- }
14298
- readSliceType() {
14299
- // skip NALu type
14300
- this.readUByte();
14301
- // discard first_mb_in_slice
14302
- this.readUEG();
14303
- // return slice_type
14304
- return this.readUEG();
14305
- }
14306
14398
  }
14307
14399
 
14308
14400
  class AvcVideoParser extends BaseVideoParser {
14309
- parseAVCPES(track, textTrack, pes, last, duration) {
14310
- const units = this.parseAVCNALu(track, pes.data);
14401
+ parsePES(track, textTrack, pes, last, duration) {
14402
+ const units = this.parseNALu(track, pes.data);
14311
14403
  let VideoSample = this.VideoSample;
14312
14404
  let push;
14313
14405
  let spsfound = false;
@@ -14332,7 +14424,7 @@ class AvcVideoParser extends BaseVideoParser {
14332
14424
  // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
14333
14425
  if (spsfound && data.length > 4) {
14334
14426
  // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
14335
- const sliceType = new ExpGolomb(data).readSliceType();
14427
+ const sliceType = this.readSliceType(data);
14336
14428
  // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
14337
14429
  // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
14338
14430
  // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
@@ -14386,8 +14478,7 @@ class AvcVideoParser extends BaseVideoParser {
14386
14478
  push = true;
14387
14479
  spsfound = true;
14388
14480
  const sps = unit.data;
14389
- const expGolombDecoder = new ExpGolomb(sps);
14390
- const config = expGolombDecoder.readSPS();
14481
+ const config = this.readSPS(sps);
14391
14482
  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]) {
14392
14483
  track.width = config.width;
14393
14484
  track.height = config.height;
@@ -14443,109 +14534,192 @@ class AvcVideoParser extends BaseVideoParser {
14443
14534
  this.VideoSample = null;
14444
14535
  }
14445
14536
  }
14446
- parseAVCNALu(track, array) {
14447
- const len = array.byteLength;
14448
- let state = track.naluState || 0;
14449
- const lastState = state;
14450
- const units = [];
14451
- let i = 0;
14452
- let value;
14453
- let overflow;
14454
- let unitType;
14455
- let lastUnitStart = -1;
14456
- let lastUnitType = 0;
14457
- // logger.log('PES:' + Hex.hexDump(array));
14537
+ getNALuType(data, offset) {
14538
+ return data[offset] & 0x1f;
14539
+ }
14540
+ readSliceType(data) {
14541
+ const eg = new ExpGolomb(data);
14542
+ // skip NALu type
14543
+ eg.readUByte();
14544
+ // discard first_mb_in_slice
14545
+ eg.readUEG();
14546
+ // return slice_type
14547
+ return eg.readUEG();
14548
+ }
14458
14549
 
14459
- if (state === -1) {
14460
- // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
14461
- lastUnitStart = 0;
14462
- // NALu type is value read from offset 0
14463
- lastUnitType = array[0] & 0x1f;
14464
- state = 0;
14465
- i = 1;
14466
- }
14467
- while (i < len) {
14468
- value = array[i++];
14469
- // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
14470
- if (!state) {
14471
- state = value ? 0 : 1;
14472
- continue;
14473
- }
14474
- if (state === 1) {
14475
- state = value ? 0 : 2;
14476
- continue;
14550
+ /**
14551
+ * The scaling list is optionally transmitted as part of a sequence parameter
14552
+ * set and is not relevant to transmuxing.
14553
+ * @param count the number of entries in this scaling list
14554
+ * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
14555
+ */
14556
+ skipScalingList(count, reader) {
14557
+ let lastScale = 8;
14558
+ let nextScale = 8;
14559
+ let deltaScale;
14560
+ for (let j = 0; j < count; j++) {
14561
+ if (nextScale !== 0) {
14562
+ deltaScale = reader.readEG();
14563
+ nextScale = (lastScale + deltaScale + 256) % 256;
14477
14564
  }
14478
- // here we have state either equal to 2 or 3
14479
- if (!value) {
14480
- state = 3;
14481
- } else if (value === 1) {
14482
- overflow = i - state - 1;
14483
- if (lastUnitStart >= 0) {
14484
- const unit = {
14485
- data: array.subarray(lastUnitStart, overflow),
14486
- type: lastUnitType
14487
- };
14488
- // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
14489
- units.push(unit);
14490
- } else {
14491
- // lastUnitStart is undefined => this is the first start code found in this PES packet
14492
- // first check if start code delimiter is overlapping between 2 PES packets,
14493
- // ie it started in last packet (lastState not zero)
14494
- // and ended at the beginning of this PES packet (i <= 4 - lastState)
14495
- const lastUnit = this.getLastNalUnit(track.samples);
14496
- if (lastUnit) {
14497
- if (lastState && i <= 4 - lastState) {
14498
- // start delimiter overlapping between PES packets
14499
- // strip start delimiter bytes from the end of last NAL unit
14500
- // check if lastUnit had a state different from zero
14501
- if (lastUnit.state) {
14502
- // strip last bytes
14503
- lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
14504
- }
14505
- }
14506
- // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
14565
+ lastScale = nextScale === 0 ? lastScale : nextScale;
14566
+ }
14567
+ }
14507
14568
 
14508
- if (overflow > 0) {
14509
- // logger.log('first NALU found with overflow:' + overflow);
14510
- lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
14511
- lastUnit.state = 0;
14569
+ /**
14570
+ * Read a sequence parameter set and return some interesting video
14571
+ * properties. A sequence parameter set is the H264 metadata that
14572
+ * describes the properties of upcoming video frames.
14573
+ * @returns an object with configuration parsed from the
14574
+ * sequence parameter set, including the dimensions of the
14575
+ * associated video frames.
14576
+ */
14577
+ readSPS(sps) {
14578
+ const eg = new ExpGolomb(sps);
14579
+ let frameCropLeftOffset = 0;
14580
+ let frameCropRightOffset = 0;
14581
+ let frameCropTopOffset = 0;
14582
+ let frameCropBottomOffset = 0;
14583
+ let numRefFramesInPicOrderCntCycle;
14584
+ let scalingListCount;
14585
+ let i;
14586
+ const readUByte = eg.readUByte.bind(eg);
14587
+ const readBits = eg.readBits.bind(eg);
14588
+ const readUEG = eg.readUEG.bind(eg);
14589
+ const readBoolean = eg.readBoolean.bind(eg);
14590
+ const skipBits = eg.skipBits.bind(eg);
14591
+ const skipEG = eg.skipEG.bind(eg);
14592
+ const skipUEG = eg.skipUEG.bind(eg);
14593
+ const skipScalingList = this.skipScalingList.bind(this);
14594
+ readUByte();
14595
+ const profileIdc = readUByte(); // profile_idc
14596
+ readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
14597
+ skipBits(3); // reserved_zero_3bits u(3),
14598
+ readUByte(); // level_idc u(8)
14599
+ skipUEG(); // seq_parameter_set_id
14600
+ // some profiles have more optional data we don't need
14601
+ if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
14602
+ const chromaFormatIdc = readUEG();
14603
+ if (chromaFormatIdc === 3) {
14604
+ skipBits(1);
14605
+ } // separate_colour_plane_flag
14606
+
14607
+ skipUEG(); // bit_depth_luma_minus8
14608
+ skipUEG(); // bit_depth_chroma_minus8
14609
+ skipBits(1); // qpprime_y_zero_transform_bypass_flag
14610
+ if (readBoolean()) {
14611
+ // seq_scaling_matrix_present_flag
14612
+ scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
14613
+ for (i = 0; i < scalingListCount; i++) {
14614
+ if (readBoolean()) {
14615
+ // seq_scaling_list_present_flag[ i ]
14616
+ if (i < 6) {
14617
+ skipScalingList(16, eg);
14618
+ } else {
14619
+ skipScalingList(64, eg);
14512
14620
  }
14513
14621
  }
14514
14622
  }
14515
- // check if we can read unit type
14516
- if (i < len) {
14517
- unitType = array[i] & 0x1f;
14518
- // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
14519
- lastUnitStart = i;
14520
- lastUnitType = unitType;
14521
- state = 0;
14522
- } else {
14523
- // not enough byte to read unit type. let's read it on next PES parsing
14524
- state = -1;
14525
- }
14526
- } else {
14527
- state = 0;
14528
14623
  }
14529
14624
  }
14530
- if (lastUnitStart >= 0 && state >= 0) {
14531
- const unit = {
14532
- data: array.subarray(lastUnitStart, len),
14533
- type: lastUnitType,
14534
- state: state
14535
- };
14536
- units.push(unit);
14537
- // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
14625
+ skipUEG(); // log2_max_frame_num_minus4
14626
+ const picOrderCntType = readUEG();
14627
+ if (picOrderCntType === 0) {
14628
+ readUEG(); // log2_max_pic_order_cnt_lsb_minus4
14629
+ } else if (picOrderCntType === 1) {
14630
+ skipBits(1); // delta_pic_order_always_zero_flag
14631
+ skipEG(); // offset_for_non_ref_pic
14632
+ skipEG(); // offset_for_top_to_bottom_field
14633
+ numRefFramesInPicOrderCntCycle = readUEG();
14634
+ for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
14635
+ skipEG();
14636
+ } // offset_for_ref_frame[ i ]
14538
14637
  }
14539
- // no NALu found
14540
- if (units.length === 0) {
14541
- // append pes.data to previous NAL unit
14542
- const lastUnit = this.getLastNalUnit(track.samples);
14543
- if (lastUnit) {
14544
- lastUnit.data = appendUint8Array(lastUnit.data, array);
14638
+ skipUEG(); // max_num_ref_frames
14639
+ skipBits(1); // gaps_in_frame_num_value_allowed_flag
14640
+ const picWidthInMbsMinus1 = readUEG();
14641
+ const picHeightInMapUnitsMinus1 = readUEG();
14642
+ const frameMbsOnlyFlag = readBits(1);
14643
+ if (frameMbsOnlyFlag === 0) {
14644
+ skipBits(1);
14645
+ } // mb_adaptive_frame_field_flag
14646
+
14647
+ skipBits(1); // direct_8x8_inference_flag
14648
+ if (readBoolean()) {
14649
+ // frame_cropping_flag
14650
+ frameCropLeftOffset = readUEG();
14651
+ frameCropRightOffset = readUEG();
14652
+ frameCropTopOffset = readUEG();
14653
+ frameCropBottomOffset = readUEG();
14654
+ }
14655
+ let pixelRatio = [1, 1];
14656
+ if (readBoolean()) {
14657
+ // vui_parameters_present_flag
14658
+ if (readBoolean()) {
14659
+ // aspect_ratio_info_present_flag
14660
+ const aspectRatioIdc = readUByte();
14661
+ switch (aspectRatioIdc) {
14662
+ case 1:
14663
+ pixelRatio = [1, 1];
14664
+ break;
14665
+ case 2:
14666
+ pixelRatio = [12, 11];
14667
+ break;
14668
+ case 3:
14669
+ pixelRatio = [10, 11];
14670
+ break;
14671
+ case 4:
14672
+ pixelRatio = [16, 11];
14673
+ break;
14674
+ case 5:
14675
+ pixelRatio = [40, 33];
14676
+ break;
14677
+ case 6:
14678
+ pixelRatio = [24, 11];
14679
+ break;
14680
+ case 7:
14681
+ pixelRatio = [20, 11];
14682
+ break;
14683
+ case 8:
14684
+ pixelRatio = [32, 11];
14685
+ break;
14686
+ case 9:
14687
+ pixelRatio = [80, 33];
14688
+ break;
14689
+ case 10:
14690
+ pixelRatio = [18, 11];
14691
+ break;
14692
+ case 11:
14693
+ pixelRatio = [15, 11];
14694
+ break;
14695
+ case 12:
14696
+ pixelRatio = [64, 33];
14697
+ break;
14698
+ case 13:
14699
+ pixelRatio = [160, 99];
14700
+ break;
14701
+ case 14:
14702
+ pixelRatio = [4, 3];
14703
+ break;
14704
+ case 15:
14705
+ pixelRatio = [3, 2];
14706
+ break;
14707
+ case 16:
14708
+ pixelRatio = [2, 1];
14709
+ break;
14710
+ case 255:
14711
+ {
14712
+ pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
14713
+ break;
14714
+ }
14715
+ }
14545
14716
  }
14546
14717
  }
14547
- track.naluState = state;
14548
- return units;
14718
+ return {
14719
+ width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
14720
+ height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
14721
+ pixelRatio: pixelRatio
14722
+ };
14549
14723
  }
14550
14724
  }
14551
14725
 
@@ -14563,7 +14737,7 @@ class SampleAesDecrypter {
14563
14737
  });
14564
14738
  }
14565
14739
  decryptBuffer(encryptedData) {
14566
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
14740
+ return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
14567
14741
  }
14568
14742
 
14569
14743
  // AAC - encrypt all full 16 bytes blocks starting from offset 16
@@ -14677,7 +14851,7 @@ class TSDemuxer {
14677
14851
  this.observer = observer;
14678
14852
  this.config = config;
14679
14853
  this.typeSupported = typeSupported;
14680
- this.videoParser = new AvcVideoParser();
14854
+ this.videoParser = null;
14681
14855
  }
14682
14856
  static probe(data) {
14683
14857
  const syncOffset = TSDemuxer.syncOffset(data);
@@ -14842,7 +15016,16 @@ class TSDemuxer {
14842
15016
  case videoPid:
14843
15017
  if (stt) {
14844
15018
  if (videoData && (pes = parsePES(videoData))) {
14845
- this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
15019
+ if (this.videoParser === null) {
15020
+ switch (videoTrack.segmentCodec) {
15021
+ case 'avc':
15022
+ this.videoParser = new AvcVideoParser();
15023
+ break;
15024
+ }
15025
+ }
15026
+ if (this.videoParser !== null) {
15027
+ this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
15028
+ }
14846
15029
  }
14847
15030
  videoData = {
14848
15031
  data: [],
@@ -15004,8 +15187,17 @@ class TSDemuxer {
15004
15187
  // try to parse last PES packets
15005
15188
  let pes;
15006
15189
  if (videoData && (pes = parsePES(videoData))) {
15007
- this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
15008
- videoTrack.pesData = null;
15190
+ if (this.videoParser === null) {
15191
+ switch (videoTrack.segmentCodec) {
15192
+ case 'avc':
15193
+ this.videoParser = new AvcVideoParser();
15194
+ break;
15195
+ }
15196
+ }
15197
+ if (this.videoParser !== null) {
15198
+ this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
15199
+ videoTrack.pesData = null;
15200
+ }
15009
15201
  } else {
15010
15202
  // either avcData null or PES truncated, keep it for next frag parsing
15011
15203
  videoTrack.pesData = videoData;
@@ -15308,7 +15500,10 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
15308
15500
  logger.warn('Unsupported EC-3 in M2TS found');
15309
15501
  break;
15310
15502
  case 0x24:
15311
- logger.warn('Unsupported HEVC in M2TS found');
15503
+ // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
15504
+ {
15505
+ logger.warn('Unsupported HEVC in M2TS found');
15506
+ }
15312
15507
  break;
15313
15508
  }
15314
15509
  // move to the next table entry
@@ -15531,6 +15726,8 @@ class MP4 {
15531
15726
  avc1: [],
15532
15727
  // codingname
15533
15728
  avcC: [],
15729
+ hvc1: [],
15730
+ hvcC: [],
15534
15731
  btrt: [],
15535
15732
  dinf: [],
15536
15733
  dref: [],
@@ -15955,8 +16152,10 @@ class MP4 {
15955
16152
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
15956
16153
  }
15957
16154
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
15958
- } else {
16155
+ } else if (track.segmentCodec === 'avc') {
15959
16156
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
16157
+ } else {
16158
+ return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
15960
16159
  }
15961
16160
  }
15962
16161
  static tkhd(track) {
@@ -16094,6 +16293,84 @@ class MP4 {
16094
16293
  const result = appendUint8Array(MP4.FTYP, movie);
16095
16294
  return result;
16096
16295
  }
16296
+ static hvc1(track) {
16297
+ const ps = track.params;
16298
+ const units = [track.vps, track.sps, track.pps];
16299
+ const NALuLengthSize = 4;
16300
+ 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]);
16301
+
16302
+ // compute hvcC size in bytes
16303
+ let length = config.length;
16304
+ for (let i = 0; i < units.length; i += 1) {
16305
+ length += 3;
16306
+ for (let j = 0; j < units[i].length; j += 1) {
16307
+ length += 2 + units[i][j].length;
16308
+ }
16309
+ }
16310
+ const hvcC = new Uint8Array(length);
16311
+ hvcC.set(config, 0);
16312
+ length = config.length;
16313
+ // append parameter set units: one vps, one or more sps and pps
16314
+ const iMax = units.length - 1;
16315
+ for (let i = 0; i < units.length; i += 1) {
16316
+ hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
16317
+ length += 3;
16318
+ for (let j = 0; j < units[i].length; j += 1) {
16319
+ hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
16320
+ length += 2;
16321
+ hvcC.set(units[i][j], length);
16322
+ length += units[i][j].length;
16323
+ }
16324
+ }
16325
+ const hvcc = MP4.box(MP4.types.hvcC, hvcC);
16326
+ const width = track.width;
16327
+ const height = track.height;
16328
+ const hSpacing = track.pixelRatio[0];
16329
+ const vSpacing = track.pixelRatio[1];
16330
+ return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
16331
+ // reserved
16332
+ 0x00, 0x00, 0x00,
16333
+ // reserved
16334
+ 0x00, 0x01,
16335
+ // data_reference_index
16336
+ 0x00, 0x00,
16337
+ // pre_defined
16338
+ 0x00, 0x00,
16339
+ // reserved
16340
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
16341
+ // pre_defined
16342
+ width >> 8 & 0xff, width & 0xff,
16343
+ // width
16344
+ height >> 8 & 0xff, height & 0xff,
16345
+ // height
16346
+ 0x00, 0x48, 0x00, 0x00,
16347
+ // horizresolution
16348
+ 0x00, 0x48, 0x00, 0x00,
16349
+ // vertresolution
16350
+ 0x00, 0x00, 0x00, 0x00,
16351
+ // reserved
16352
+ 0x00, 0x01,
16353
+ // frame_count
16354
+ 0x12, 0x64, 0x61, 0x69, 0x6c,
16355
+ // dailymotion/hls.js
16356
+ 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,
16357
+ // compressorname
16358
+ 0x00, 0x18,
16359
+ // depth = 24
16360
+ 0x11, 0x11]),
16361
+ // pre_defined = -1
16362
+ hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
16363
+ // bufferSizeDB
16364
+ 0x00, 0x2d, 0xc6, 0xc0,
16365
+ // maxBitrate
16366
+ 0x00, 0x2d, 0xc6, 0xc0])),
16367
+ // avgBitrate
16368
+ MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
16369
+ // hSpacing
16370
+ hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
16371
+ // vSpacing
16372
+ vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
16373
+ }
16097
16374
  }
16098
16375
  MP4.types = void 0;
16099
16376
  MP4.HDLR_TYPES = void 0;
@@ -16469,9 +16746,9 @@ class MP4Remuxer {
16469
16746
  const foundOverlap = delta < -1;
16470
16747
  if (foundHole || foundOverlap) {
16471
16748
  if (foundHole) {
16472
- logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
16749
+ logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
16473
16750
  } else {
16474
- logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
16751
+ logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
16475
16752
  }
16476
16753
  if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
16477
16754
  firstDTS = nextAvcDts;
@@ -16480,12 +16757,24 @@ class MP4Remuxer {
16480
16757
  inputSamples[0].dts = firstDTS;
16481
16758
  inputSamples[0].pts = firstPTS;
16482
16759
  } else {
16760
+ let isPTSOrderRetained = true;
16483
16761
  for (let i = 0; i < inputSamples.length; i++) {
16484
- if (inputSamples[i].dts > firstPTS) {
16762
+ if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
16485
16763
  break;
16486
16764
  }
16765
+ const prevPTS = inputSamples[i].pts;
16487
16766
  inputSamples[i].dts -= delta;
16488
16767
  inputSamples[i].pts -= delta;
16768
+
16769
+ // check to see if this sample's PTS order has changed
16770
+ // relative to the next one
16771
+ if (i < inputSamples.length - 1) {
16772
+ const nextSamplePTS = inputSamples[i + 1].pts;
16773
+ const currentSamplePTS = inputSamples[i].pts;
16774
+ const currentOrder = nextSamplePTS <= currentSamplePTS;
16775
+ const prevOrder = nextSamplePTS <= prevPTS;
16776
+ isPTSOrderRetained = currentOrder == prevOrder;
16777
+ }
16489
16778
  }
16490
16779
  }
16491
16780
  logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
@@ -16633,7 +16922,7 @@ class MP4Remuxer {
16633
16922
  }
16634
16923
  }
16635
16924
  }
16636
- // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
16925
+ // next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
16637
16926
  mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
16638
16927
  this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
16639
16928
  this.videoSampleDuration = mp4SampleDuration;
@@ -16766,7 +17055,7 @@ class MP4Remuxer {
16766
17055
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
16767
17056
  for (let j = 0; j < missing; j++) {
16768
17057
  const newStamp = Math.max(nextPts, 0);
16769
- let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
17058
+ let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
16770
17059
  if (!fillFrame) {
16771
17060
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
16772
17061
  fillFrame = sample.unit.subarray();
@@ -16894,7 +17183,7 @@ class MP4Remuxer {
16894
17183
  // samples count of this segment's duration
16895
17184
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
16896
17185
  // silent frame
16897
- const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
17186
+ const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
16898
17187
  logger.warn('[mp4-remuxer]: remux empty Audio');
16899
17188
  // Can't remux if we can't generate a silent frame...
16900
17189
  if (!silentFrame) {
@@ -17285,13 +17574,15 @@ class Transmuxer {
17285
17574
  initSegmentData
17286
17575
  } = transmuxConfig;
17287
17576
  const keyData = getEncryptionType(uintData, decryptdata);
17288
- if (keyData && keyData.method === 'AES-128') {
17577
+ if (keyData && isFullSegmentEncryption(keyData.method)) {
17289
17578
  const decrypter = this.getDecrypter();
17579
+ const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
17580
+
17290
17581
  // Software decryption is synchronous; webCrypto is not
17291
17582
  if (decrypter.isSync()) {
17292
17583
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
17293
17584
  // data is handled in the flush() call
17294
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
17585
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
17295
17586
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
17296
17587
  const loadingParts = chunkMeta.part > -1;
17297
17588
  if (loadingParts) {
@@ -17303,7 +17594,7 @@ class Transmuxer {
17303
17594
  }
17304
17595
  uintData = new Uint8Array(decryptedData);
17305
17596
  } else {
17306
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
17597
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
17307
17598
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
17308
17599
  // the decrypted data has been transmuxed
17309
17600
  const result = this.push(decryptedData, null, chunkMeta);
@@ -17957,14 +18248,7 @@ class TransmuxerInterface {
17957
18248
  this.observer = new EventEmitter();
17958
18249
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
17959
18250
  this.observer.on(Events.ERROR, forwardMessage);
17960
- const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
17961
- isTypeSupported: () => false
17962
- };
17963
- const m2tsTypeSupported = {
17964
- mpeg: MediaSource.isTypeSupported('audio/mpeg'),
17965
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
17966
- ac3: false
17967
- };
18251
+ const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
17968
18252
 
17969
18253
  // navigator.vendor is not always available in Web Worker
17970
18254
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -18228,8 +18512,9 @@ const STALL_MINIMUM_DURATION_MS = 250;
18228
18512
  const MAX_START_GAP_JUMP = 2.0;
18229
18513
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
18230
18514
  const SKIP_BUFFER_RANGE_START = 0.05;
18231
- class GapController {
18515
+ class GapController extends Logger {
18232
18516
  constructor(config, media, fragmentTracker, hls) {
18517
+ super('gap-controller', hls.logger);
18233
18518
  this.config = void 0;
18234
18519
  this.media = null;
18235
18520
  this.fragmentTracker = void 0;
@@ -18239,6 +18524,7 @@ class GapController {
18239
18524
  this.stalled = null;
18240
18525
  this.moved = false;
18241
18526
  this.seeking = false;
18527
+ this.ended = 0;
18242
18528
  this.config = config;
18243
18529
  this.media = media;
18244
18530
  this.fragmentTracker = fragmentTracker;
@@ -18256,7 +18542,7 @@ class GapController {
18256
18542
  *
18257
18543
  * @param lastCurrentTime - Previously read playhead position
18258
18544
  */
18259
- poll(lastCurrentTime, activeFrag) {
18545
+ poll(lastCurrentTime, activeFrag, levelDetails, state) {
18260
18546
  const {
18261
18547
  config,
18262
18548
  media,
@@ -18275,6 +18561,7 @@ class GapController {
18275
18561
 
18276
18562
  // The playhead is moving, no-op
18277
18563
  if (currentTime !== lastCurrentTime) {
18564
+ this.ended = 0;
18278
18565
  this.moved = true;
18279
18566
  if (!seeking) {
18280
18567
  this.nudgeRetry = 0;
@@ -18283,7 +18570,7 @@ class GapController {
18283
18570
  // The playhead is now moving, but was previously stalled
18284
18571
  if (this.stallReported) {
18285
18572
  const _stalledDuration = self.performance.now() - stalled;
18286
- logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
18573
+ this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
18287
18574
  this.stallReported = false;
18288
18575
  }
18289
18576
  this.stalled = null;
@@ -18319,7 +18606,6 @@ class GapController {
18319
18606
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
18320
18607
  // The addition poll gives the browser a chance to jump the gap for us
18321
18608
  if (!this.moved && this.stalled !== null) {
18322
- var _level$details;
18323
18609
  // There is no playable buffer (seeked, waiting for buffer)
18324
18610
  const isBuffered = bufferInfo.len > 0;
18325
18611
  if (!isBuffered && !nextStart) {
@@ -18331,9 +18617,8 @@ class GapController {
18331
18617
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
18332
18618
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
18333
18619
  // that begins over 1 target duration after the video start position.
18334
- const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
18335
- const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
18336
- const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
18620
+ const isLive = !!(levelDetails != null && levelDetails.live);
18621
+ const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
18337
18622
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
18338
18623
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
18339
18624
  if (!media.paused) {
@@ -18351,6 +18636,17 @@ class GapController {
18351
18636
  }
18352
18637
  const stalledDuration = tnow - stalled;
18353
18638
  if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
18639
+ // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
18640
+ if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
18641
+ if (stalledDuration < 1000 || this.ended) {
18642
+ return;
18643
+ }
18644
+ this.ended = currentTime;
18645
+ this.hls.trigger(Events.MEDIA_ENDED, {
18646
+ stalled: true
18647
+ });
18648
+ return;
18649
+ }
18354
18650
  // Report stalling after trying to fix
18355
18651
  this._reportStall(bufferInfo);
18356
18652
  if (!this.media) {
@@ -18394,7 +18690,7 @@ class GapController {
18394
18690
  // needs to cross some sort of threshold covering all source-buffers content
18395
18691
  // to start playing properly.
18396
18692
  if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
18397
- logger.warn('Trying to nudge playhead over buffer-hole');
18693
+ this.warn('Trying to nudge playhead over buffer-hole');
18398
18694
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
18399
18695
  // We only try to jump the hole if it's under the configured size
18400
18696
  // Reset stalled so to rearm watchdog timer
@@ -18418,7 +18714,7 @@ class GapController {
18418
18714
  // Report stalled error once
18419
18715
  this.stallReported = true;
18420
18716
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
18421
- logger.warn(error.message);
18717
+ this.warn(error.message);
18422
18718
  hls.trigger(Events.ERROR, {
18423
18719
  type: ErrorTypes.MEDIA_ERROR,
18424
18720
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -18486,7 +18782,7 @@ class GapController {
18486
18782
  }
18487
18783
  }
18488
18784
  const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
18489
- logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
18785
+ this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
18490
18786
  this.moved = true;
18491
18787
  this.stalled = null;
18492
18788
  media.currentTime = targetTime;
@@ -18527,7 +18823,7 @@ class GapController {
18527
18823
  const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
18528
18824
  // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
18529
18825
  const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
18530
- logger.warn(error.message);
18826
+ this.warn(error.message);
18531
18827
  media.currentTime = targetTime;
18532
18828
  hls.trigger(Events.ERROR, {
18533
18829
  type: ErrorTypes.MEDIA_ERROR,
@@ -18537,7 +18833,7 @@ class GapController {
18537
18833
  });
18538
18834
  } else {
18539
18835
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
18540
- logger.error(error.message);
18836
+ this.error(error.message);
18541
18837
  hls.trigger(Events.ERROR, {
18542
18838
  type: ErrorTypes.MEDIA_ERROR,
18543
18839
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -18552,7 +18848,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
18552
18848
 
18553
18849
  class StreamController extends BaseStreamController {
18554
18850
  constructor(hls, fragmentTracker, keyLoader) {
18555
- super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
18851
+ super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
18556
18852
  this.audioCodecSwap = false;
18557
18853
  this.gapController = null;
18558
18854
  this.level = -1;
@@ -18560,27 +18856,43 @@ class StreamController extends BaseStreamController {
18560
18856
  this.altAudio = false;
18561
18857
  this.audioOnly = false;
18562
18858
  this.fragPlaying = null;
18563
- this.onvplaying = null;
18564
- this.onvseeked = null;
18565
18859
  this.fragLastKbps = 0;
18566
18860
  this.couldBacktrack = false;
18567
18861
  this.backtrackFragment = null;
18568
18862
  this.audioCodecSwitch = false;
18569
18863
  this.videoBuffer = null;
18570
- this._registerListeners();
18864
+ this.onMediaPlaying = () => {
18865
+ // tick to speed up FRAG_CHANGED triggering
18866
+ this.tick();
18867
+ };
18868
+ this.onMediaSeeked = () => {
18869
+ const media = this.media;
18870
+ const currentTime = media ? media.currentTime : null;
18871
+ if (isFiniteNumber(currentTime)) {
18872
+ this.log(`Media seeked to ${currentTime.toFixed(3)}`);
18873
+ }
18874
+
18875
+ // If seeked was issued before buffer was appended do not tick immediately
18876
+ const bufferInfo = this.getMainFwdBufferInfo();
18877
+ if (bufferInfo === null || bufferInfo.len === 0) {
18878
+ this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
18879
+ return;
18880
+ }
18881
+
18882
+ // tick to speed up FRAG_CHANGED triggering
18883
+ this.tick();
18884
+ };
18885
+ this.registerListeners();
18571
18886
  }
18572
- _registerListeners() {
18887
+ registerListeners() {
18888
+ super.registerListeners();
18573
18889
  const {
18574
18890
  hls
18575
18891
  } = this;
18576
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
18577
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
18578
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18579
18892
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18580
18893
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
18581
18894
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
18582
18895
  hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
18583
- hls.on(Events.ERROR, this.onError, this);
18584
18896
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
18585
18897
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
18586
18898
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -18588,17 +18900,14 @@ class StreamController extends BaseStreamController {
18588
18900
  hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
18589
18901
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
18590
18902
  }
18591
- _unregisterListeners() {
18903
+ unregisterListeners() {
18904
+ super.unregisterListeners();
18592
18905
  const {
18593
18906
  hls
18594
18907
  } = this;
18595
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
18596
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
18597
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18598
18908
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18599
18909
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
18600
18910
  hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
18601
- hls.off(Events.ERROR, this.onError, this);
18602
18911
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
18603
18912
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
18604
18913
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -18607,7 +18916,9 @@ class StreamController extends BaseStreamController {
18607
18916
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
18608
18917
  }
18609
18918
  onHandlerDestroying() {
18610
- this._unregisterListeners();
18919
+ // @ts-ignore
18920
+ this.onMediaPlaying = this.onMediaSeeked = null;
18921
+ this.unregisterListeners();
18611
18922
  super.onHandlerDestroying();
18612
18923
  }
18613
18924
  startLoad(startPosition) {
@@ -18932,20 +19243,17 @@ class StreamController extends BaseStreamController {
18932
19243
  onMediaAttached(event, data) {
18933
19244
  super.onMediaAttached(event, data);
18934
19245
  const media = data.media;
18935
- this.onvplaying = this.onMediaPlaying.bind(this);
18936
- this.onvseeked = this.onMediaSeeked.bind(this);
18937
- media.addEventListener('playing', this.onvplaying);
18938
- media.addEventListener('seeked', this.onvseeked);
19246
+ media.addEventListener('playing', this.onMediaPlaying);
19247
+ media.addEventListener('seeked', this.onMediaSeeked);
18939
19248
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
18940
19249
  }
18941
19250
  onMediaDetaching() {
18942
19251
  const {
18943
19252
  media
18944
19253
  } = this;
18945
- if (media && this.onvplaying && this.onvseeked) {
18946
- media.removeEventListener('playing', this.onvplaying);
18947
- media.removeEventListener('seeked', this.onvseeked);
18948
- this.onvplaying = this.onvseeked = null;
19254
+ if (media) {
19255
+ media.removeEventListener('playing', this.onMediaPlaying);
19256
+ media.removeEventListener('seeked', this.onMediaSeeked);
18949
19257
  this.videoBuffer = null;
18950
19258
  }
18951
19259
  this.fragPlaying = null;
@@ -18955,27 +19263,6 @@ class StreamController extends BaseStreamController {
18955
19263
  }
18956
19264
  super.onMediaDetaching();
18957
19265
  }
18958
- onMediaPlaying() {
18959
- // tick to speed up FRAG_CHANGED triggering
18960
- this.tick();
18961
- }
18962
- onMediaSeeked() {
18963
- const media = this.media;
18964
- const currentTime = media ? media.currentTime : null;
18965
- if (isFiniteNumber(currentTime)) {
18966
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
18967
- }
18968
-
18969
- // If seeked was issued before buffer was appended do not tick immediately
18970
- const bufferInfo = this.getMainFwdBufferInfo();
18971
- if (bufferInfo === null || bufferInfo.len === 0) {
18972
- this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
18973
- return;
18974
- }
18975
-
18976
- // tick to speed up FRAG_CHANGED triggering
18977
- this.tick();
18978
- }
18979
19266
  onManifestLoading() {
18980
19267
  // reset buffer on manifest loading
18981
19268
  this.log('Trigger BUFFER_RESET');
@@ -19267,8 +19554,10 @@ class StreamController extends BaseStreamController {
19267
19554
  }
19268
19555
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
19269
19556
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
19270
- const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
19271
- gapController.poll(this.lastCurrentTime, activeFrag);
19557
+ const state = this.state;
19558
+ const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
19559
+ const levelDetails = this.getLevelDetails();
19560
+ gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
19272
19561
  }
19273
19562
  this.lastCurrentTime = media.currentTime;
19274
19563
  }
@@ -19706,7 +19995,7 @@ class Hls {
19706
19995
  * Get the video-dev/hls.js package version.
19707
19996
  */
19708
19997
  static get version() {
19709
- return "1.5.6";
19998
+ return "1.5.7-0.canary.10014";
19710
19999
  }
19711
20000
 
19712
20001
  /**
@@ -19769,9 +20058,12 @@ class Hls {
19769
20058
  * The configuration object provided on player instantiation.
19770
20059
  */
19771
20060
  this.userConfig = void 0;
20061
+ /**
20062
+ * The logger functions used by this player instance, configured on player instantiation.
20063
+ */
20064
+ this.logger = void 0;
19772
20065
  this.coreComponents = void 0;
19773
20066
  this.networkControllers = void 0;
19774
- this.started = false;
19775
20067
  this._emitter = new EventEmitter();
19776
20068
  this._autoLevelCapping = -1;
19777
20069
  this._maxHdcpLevel = null;
@@ -19788,11 +20080,11 @@ class Hls {
19788
20080
  this._media = null;
19789
20081
  this.url = null;
19790
20082
  this.triggeringException = void 0;
19791
- enableLogs(userConfig.debug || false, 'Hls instance');
19792
- const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
20083
+ const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
20084
+ const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
19793
20085
  this.userConfig = userConfig;
19794
20086
  if (config.progressive) {
19795
- enableStreamingMode(config);
20087
+ enableStreamingMode(config, logger);
19796
20088
  }
19797
20089
 
19798
20090
  // core controllers and network loaders
@@ -19891,7 +20183,7 @@ class Hls {
19891
20183
  try {
19892
20184
  return this.emit(event, event, eventObject);
19893
20185
  } catch (error) {
19894
- logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
20186
+ this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
19895
20187
  // Prevent recursion in error event handlers that throw #5497
19896
20188
  if (!this.triggeringException) {
19897
20189
  this.triggeringException = true;
@@ -19917,7 +20209,7 @@ class Hls {
19917
20209
  * Dispose of the instance
19918
20210
  */
19919
20211
  destroy() {
19920
- logger.log('destroy');
20212
+ this.logger.log('destroy');
19921
20213
  this.trigger(Events.DESTROYING, undefined);
19922
20214
  this.detachMedia();
19923
20215
  this.removeAllListeners();
@@ -19938,7 +20230,7 @@ class Hls {
19938
20230
  * Attaches Hls.js to a media element
19939
20231
  */
19940
20232
  attachMedia(media) {
19941
- logger.log('attachMedia');
20233
+ this.logger.log('attachMedia');
19942
20234
  this._media = media;
19943
20235
  this.trigger(Events.MEDIA_ATTACHING, {
19944
20236
  media: media
@@ -19949,7 +20241,7 @@ class Hls {
19949
20241
  * Detach Hls.js from the media
19950
20242
  */
19951
20243
  detachMedia() {
19952
- logger.log('detachMedia');
20244
+ this.logger.log('detachMedia');
19953
20245
  this.trigger(Events.MEDIA_DETACHING, undefined);
19954
20246
  this._media = null;
19955
20247
  }
@@ -19966,7 +20258,7 @@ class Hls {
19966
20258
  });
19967
20259
  this._autoLevelCapping = -1;
19968
20260
  this._maxHdcpLevel = null;
19969
- logger.log(`loadSource:${loadingSource}`);
20261
+ this.logger.log(`loadSource:${loadingSource}`);
19970
20262
  if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
19971
20263
  this.detachMedia();
19972
20264
  this.attachMedia(media);
@@ -19985,8 +20277,7 @@ class Hls {
19985
20277
  * Defaults to -1 (None: starts from earliest point)
19986
20278
  */
19987
20279
  startLoad(startPosition = -1) {
19988
- logger.log(`startLoad(${startPosition})`);
19989
- this.started = true;
20280
+ this.logger.log(`startLoad(${startPosition})`);
19990
20281
  this.networkControllers.forEach(controller => {
19991
20282
  controller.startLoad(startPosition);
19992
20283
  });
@@ -19996,34 +20287,31 @@ class Hls {
19996
20287
  * Stop loading of any stream data.
19997
20288
  */
19998
20289
  stopLoad() {
19999
- logger.log('stopLoad');
20000
- this.started = false;
20290
+ this.logger.log('stopLoad');
20001
20291
  this.networkControllers.forEach(controller => {
20002
20292
  controller.stopLoad();
20003
20293
  });
20004
20294
  }
20005
20295
 
20006
20296
  /**
20007
- * Resumes stream controller segment loading if previously started.
20297
+ * Resumes stream controller segment loading after `pauseBuffering` has been called.
20008
20298
  */
20009
20299
  resumeBuffering() {
20010
- if (this.started) {
20011
- this.networkControllers.forEach(controller => {
20012
- if ('fragmentLoader' in controller) {
20013
- controller.startLoad(-1);
20014
- }
20015
- });
20016
- }
20300
+ this.networkControllers.forEach(controller => {
20301
+ if (controller.resumeBuffering) {
20302
+ controller.resumeBuffering();
20303
+ }
20304
+ });
20017
20305
  }
20018
20306
 
20019
20307
  /**
20020
- * Stops stream controller segment loading without changing 'started' state like stopLoad().
20308
+ * Prevents stream controller from loading new segments until `resumeBuffering` is called.
20021
20309
  * This allows for media buffering to be paused without interupting playlist loading.
20022
20310
  */
20023
20311
  pauseBuffering() {
20024
20312
  this.networkControllers.forEach(controller => {
20025
- if ('fragmentLoader' in controller) {
20026
- controller.stopLoad();
20313
+ if (controller.pauseBuffering) {
20314
+ controller.pauseBuffering();
20027
20315
  }
20028
20316
  });
20029
20317
  }
@@ -20032,7 +20320,7 @@ class Hls {
20032
20320
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
20033
20321
  */
20034
20322
  swapAudioCodec() {
20035
- logger.log('swapAudioCodec');
20323
+ this.logger.log('swapAudioCodec');
20036
20324
  this.streamController.swapAudioCodec();
20037
20325
  }
20038
20326
 
@@ -20043,7 +20331,7 @@ class Hls {
20043
20331
  * Automatic recovery of media-errors by this process is configurable.
20044
20332
  */
20045
20333
  recoverMediaError() {
20046
- logger.log('recoverMediaError');
20334
+ this.logger.log('recoverMediaError');
20047
20335
  const media = this._media;
20048
20336
  this.detachMedia();
20049
20337
  if (media) {
@@ -20073,7 +20361,7 @@ class Hls {
20073
20361
  * 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.
20074
20362
  */
20075
20363
  set currentLevel(newLevel) {
20076
- logger.log(`set currentLevel:${newLevel}`);
20364
+ this.logger.log(`set currentLevel:${newLevel}`);
20077
20365
  this.levelController.manualLevel = newLevel;
20078
20366
  this.streamController.immediateLevelSwitch();
20079
20367
  }
@@ -20092,7 +20380,7 @@ class Hls {
20092
20380
  * @param newLevel - Pass -1 for automatic level selection
20093
20381
  */
20094
20382
  set nextLevel(newLevel) {
20095
- logger.log(`set nextLevel:${newLevel}`);
20383
+ this.logger.log(`set nextLevel:${newLevel}`);
20096
20384
  this.levelController.manualLevel = newLevel;
20097
20385
  this.streamController.nextLevelSwitch();
20098
20386
  }
@@ -20111,7 +20399,7 @@ class Hls {
20111
20399
  * @param newLevel - Pass -1 for automatic level selection
20112
20400
  */
20113
20401
  set loadLevel(newLevel) {
20114
- logger.log(`set loadLevel:${newLevel}`);
20402
+ this.logger.log(`set loadLevel:${newLevel}`);
20115
20403
  this.levelController.manualLevel = newLevel;
20116
20404
  }
20117
20405
 
@@ -20142,7 +20430,7 @@ class Hls {
20142
20430
  * Sets "first-level", see getter.
20143
20431
  */
20144
20432
  set firstLevel(newLevel) {
20145
- logger.log(`set firstLevel:${newLevel}`);
20433
+ this.logger.log(`set firstLevel:${newLevel}`);
20146
20434
  this.levelController.firstLevel = newLevel;
20147
20435
  }
20148
20436
 
@@ -20167,7 +20455,7 @@ class Hls {
20167
20455
  * (determined from download of first segment)
20168
20456
  */
20169
20457
  set startLevel(newLevel) {
20170
- logger.log(`set startLevel:${newLevel}`);
20458
+ this.logger.log(`set startLevel:${newLevel}`);
20171
20459
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
20172
20460
  if (newLevel !== -1) {
20173
20461
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -20242,7 +20530,7 @@ class Hls {
20242
20530
  */
20243
20531
  set autoLevelCapping(newLevel) {
20244
20532
  if (this._autoLevelCapping !== newLevel) {
20245
- logger.log(`set autoLevelCapping:${newLevel}`);
20533
+ this.logger.log(`set autoLevelCapping:${newLevel}`);
20246
20534
  this._autoLevelCapping = newLevel;
20247
20535
  this.levelController.checkMaxAutoUpdated();
20248
20536
  }
@@ -20521,5 +20809,5 @@ var KeySystemFormats = empty.KeySystemFormats;
20521
20809
  var KeySystems = empty.KeySystems;
20522
20810
  var SubtitleStreamController = empty.SubtitleStreamController;
20523
20811
  var TimelineController = empty.TimelineController;
20524
- 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 };
20812
+ 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 };
20525
20813
  //# sourceMappingURL=hls.light.mjs.map