hls.js 1.5.5-0.canary.9995 → 1.5.5

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 +0 -1
  2. package/dist/hls-demo.js +0 -10
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +1134 -2043
  5. package/dist/hls.js.d.ts +50 -65
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +852 -1141
  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 +686 -974
  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 +847 -1741
  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 +20 -20
  20. package/src/config.ts +2 -3
  21. package/src/controller/abr-controller.ts +20 -21
  22. package/src/controller/audio-stream-controller.ts +16 -15
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +8 -20
  25. package/src/controller/base-stream-controller.ts +33 -149
  26. package/src/controller/buffer-controller.ts +11 -11
  27. package/src/controller/cap-level-controller.ts +2 -1
  28. package/src/controller/cmcd-controller.ts +6 -27
  29. package/src/controller/content-steering-controller.ts +6 -8
  30. package/src/controller/eme-controller.ts +22 -9
  31. package/src/controller/error-controller.ts +8 -6
  32. package/src/controller/fps-controller.ts +3 -2
  33. package/src/controller/gap-controller.ts +16 -43
  34. package/src/controller/latency-controller.ts +11 -9
  35. package/src/controller/level-controller.ts +18 -12
  36. package/src/controller/stream-controller.ts +32 -25
  37. package/src/controller/subtitle-stream-controller.ts +14 -13
  38. package/src/controller/subtitle-track-controller.ts +3 -5
  39. package/src/controller/timeline-controller.ts +30 -23
  40. package/src/crypt/aes-crypto.ts +2 -21
  41. package/src/crypt/decrypter.ts +18 -32
  42. package/src/crypt/fast-aes-key.ts +5 -24
  43. package/src/demux/audio/adts.ts +4 -9
  44. package/src/demux/sample-aes.ts +0 -2
  45. package/src/demux/transmuxer-interface.ts +12 -4
  46. package/src/demux/transmuxer-worker.ts +4 -4
  47. package/src/demux/transmuxer.ts +3 -16
  48. package/src/demux/tsdemuxer.ts +37 -71
  49. package/src/demux/video/avc-video-parser.ts +119 -208
  50. package/src/demux/video/base-video-parser.ts +2 -134
  51. package/src/demux/video/exp-golomb.ts +208 -0
  52. package/src/events.ts +0 -7
  53. package/src/hls.ts +34 -42
  54. package/src/loader/fragment-loader.ts +2 -9
  55. package/src/loader/key-loader.ts +0 -2
  56. package/src/loader/level-key.ts +9 -10
  57. package/src/loader/playlist-loader.ts +5 -4
  58. package/src/remux/mp4-generator.ts +1 -196
  59. package/src/remux/mp4-remuxer.ts +7 -23
  60. package/src/task-loop.ts +2 -5
  61. package/src/types/component-api.ts +0 -2
  62. package/src/types/demuxer.ts +0 -3
  63. package/src/types/events.ts +0 -4
  64. package/src/utils/codecs.ts +4 -33
  65. package/src/utils/logger.ts +24 -54
  66. package/src/crypt/decrypter-aes-mode.ts +0 -4
  67. package/src/demux/video/hevc-video-parser.ts +0 -746
  68. package/src/utils/encryption-methods-util.ts +0 -21
@@ -256,7 +256,6 @@ 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";
260
259
  Events["BUFFER_RESET"] = "hlsBufferReset";
261
260
  Events["BUFFER_CODECS"] = "hlsBufferCodecs";
262
261
  Events["BUFFER_CREATED"] = "hlsBufferCreated";
@@ -370,6 +369,58 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
370
369
  return ErrorDetails;
371
370
  }({});
372
371
 
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.5"}`);
415
+ } catch (e) {
416
+ exportedLogger = fakeLogger;
417
+ }
418
+ } else {
419
+ exportedLogger = fakeLogger;
420
+ }
421
+ }
422
+ const logger = exportedLogger;
423
+
373
424
  const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
374
425
  const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
375
426
 
@@ -451,79 +502,6 @@ class AttrList {
451
502
  }
452
503
  }
453
504
 
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.5-0.canary.9995"}`);
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
-
527
505
  // Avoid exporting const enum so that these values can be inlined
528
506
 
529
507
  function isDateRangeCueAttribute(attrName) {
@@ -1013,30 +991,10 @@ class LevelDetails {
1013
991
  }
1014
992
  }
1015
993
 
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
-
1036
994
  // This file is inserted as a shim for modules which we do not want to include into the distro.
1037
995
  // This replacement is done in the "alias" plugin of the rollup config.
1038
996
  var empty = undefined;
1039
- var HevcVideoParser = /*@__PURE__*/getDefaultExportFromCjs(empty);
997
+ var Cues = /*@__PURE__*/getDefaultExportFromCjs(empty);
1040
998
 
1041
999
  function sliceUint8(array, start, end) {
1042
1000
  // @ts-expect-error This polyfills IE11 usage of Uint8Array slice.
@@ -2473,12 +2431,12 @@ class LevelKey {
2473
2431
  this.keyFormatVersions = formatversions;
2474
2432
  this.iv = iv;
2475
2433
  this.encrypted = method ? method !== 'NONE' : false;
2476
- this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
2434
+ this.isCommonEncryption = this.encrypted && method !== 'AES-128';
2477
2435
  }
2478
2436
  isSupported() {
2479
2437
  // If it's Segment encryption or No encryption, just select that key system
2480
2438
  if (this.method) {
2481
- if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
2439
+ if (this.method === 'AES-128' || this.method === 'NONE') {
2482
2440
  return true;
2483
2441
  }
2484
2442
  if (this.keyFormat === 'identity') {
@@ -2492,13 +2450,14 @@ class LevelKey {
2492
2450
  if (!this.encrypted || !this.uri) {
2493
2451
  return null;
2494
2452
  }
2495
- if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
2453
+ if (this.method === 'AES-128' && this.uri && !this.iv) {
2496
2454
  if (typeof sn !== 'number') {
2497
2455
  // We are fetching decryption data for a initialization segment
2498
- // If the segment was encrypted with AES-128/256
2456
+ // If the segment was encrypted with AES-128
2499
2457
  // It must have an IV defined. We cannot substitute the Segment Number in.
2500
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2501
-
2458
+ if (this.method === 'AES-128' && !this.iv) {
2459
+ logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2460
+ }
2502
2461
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
2503
2462
  sn = 0;
2504
2463
  }
@@ -2645,28 +2604,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
2645
2604
  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
2646
2605
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
2647
2606
  }
2607
+
2608
+ // Idealy fLaC and Opus would be first (spec-compliant) but
2609
+ // some browsers will report that fLaC is supported then fail.
2610
+ // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
2648
2611
  const codecsToCheck = {
2649
- // Idealy fLaC and Opus would be first (spec-compliant) but
2650
- // some browsers will report that fLaC is supported then fail.
2651
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
2652
2612
  flac: ['flac', 'fLaC', 'FLAC'],
2653
- opus: ['opus', 'Opus'],
2654
- // Replace audio codec info if browser does not support mp4a.40.34,
2655
- // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
2656
- 'mp4a.40.34': ['mp3']
2613
+ opus: ['opus', 'Opus']
2657
2614
  }[lowerCaseCodec];
2658
2615
  for (let i = 0; i < codecsToCheck.length; i++) {
2659
- var _getMediaSource;
2660
2616
  if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
2661
2617
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
2662
2618
  return codecsToCheck[i];
2663
- } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
2664
- return '';
2665
2619
  }
2666
2620
  }
2667
2621
  return lowerCaseCodec;
2668
2622
  }
2669
- const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
2623
+ const AUDIO_CODEC_REGEXP = /flac|opus/i;
2670
2624
  function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
2671
2625
  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
2672
2626
  }
@@ -2689,16 +2643,6 @@ function convertAVC1ToAVCOTI(codec) {
2689
2643
  }
2690
2644
  return codec;
2691
2645
  }
2692
- function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
2693
- const MediaSource = getMediaSource(preferManagedMediaSource) || {
2694
- isTypeSupported: () => false
2695
- };
2696
- return {
2697
- mpeg: MediaSource.isTypeSupported('audio/mpeg'),
2698
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
2699
- ac3: false
2700
- };
2701
- }
2702
2646
 
2703
2647
  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;
2704
2648
  const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -3499,10 +3443,10 @@ class PlaylistLoader {
3499
3443
  const loaderContext = loader.context;
3500
3444
  if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
3501
3445
  // same URL can't overlap
3502
- this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
3446
+ logger.trace('[playlist-loader]: playlist request ongoing');
3503
3447
  return;
3504
3448
  }
3505
- this.hls.logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3449
+ logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3506
3450
  loader.abort();
3507
3451
  }
3508
3452
 
@@ -3612,7 +3556,7 @@ class PlaylistLoader {
3612
3556
  // alt audio rendition in which quality levels (main)
3613
3557
  // contains both audio+video. but with mixed audio track not signaled
3614
3558
  if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
3615
- this.hls.logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
3559
+ logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
3616
3560
  audioTracks.unshift({
3617
3561
  type: 'main',
3618
3562
  name: 'main',
@@ -3711,7 +3655,7 @@ class PlaylistLoader {
3711
3655
  message += ` id: ${context.id} group-id: "${context.groupId}"`;
3712
3656
  }
3713
3657
  const error = new Error(message);
3714
- this.hls.logger.warn(`[playlist-loader]: ${message}`);
3658
+ logger.warn(`[playlist-loader]: ${message}`);
3715
3659
  let details = ErrorDetails.UNKNOWN;
3716
3660
  let fatal = false;
3717
3661
  const loader = this.getInternalLoader(context);
@@ -4276,47 +4220,7 @@ class LatencyController {
4276
4220
  this.currentTime = 0;
4277
4221
  this.stallCount = 0;
4278
4222
  this._latency = null;
4279
- this.onTimeupdate = () => {
4280
- const {
4281
- media,
4282
- levelDetails
4283
- } = this;
4284
- if (!media || !levelDetails) {
4285
- return;
4286
- }
4287
- this.currentTime = media.currentTime;
4288
- const latency = this.computeLatency();
4289
- if (latency === null) {
4290
- return;
4291
- }
4292
- this._latency = latency;
4293
-
4294
- // Adapt playbackRate to meet target latency in low-latency mode
4295
- const {
4296
- lowLatencyMode,
4297
- maxLiveSyncPlaybackRate
4298
- } = this.config;
4299
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4300
- return;
4301
- }
4302
- const targetLatency = this.targetLatency;
4303
- if (targetLatency === null) {
4304
- return;
4305
- }
4306
- const distanceFromTarget = latency - targetLatency;
4307
- // Only adjust playbackRate when within one target duration of targetLatency
4308
- // and more than one second from under-buffering.
4309
- // Playback further than one target duration from target can be considered DVR playback.
4310
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4311
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4312
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4313
- const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4314
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4315
- media.playbackRate = Math.min(max, Math.max(1, rate));
4316
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4317
- media.playbackRate = 1;
4318
- }
4319
- };
4223
+ this.timeupdateHandler = () => this.timeupdate();
4320
4224
  this.hls = hls;
4321
4225
  this.config = hls.config;
4322
4226
  this.registerListeners();
@@ -4408,7 +4312,7 @@ class LatencyController {
4408
4312
  this.onMediaDetaching();
4409
4313
  this.levelDetails = null;
4410
4314
  // @ts-ignore
4411
- this.hls = null;
4315
+ this.hls = this.timeupdateHandler = null;
4412
4316
  }
4413
4317
  registerListeners() {
4414
4318
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -4426,11 +4330,11 @@ class LatencyController {
4426
4330
  }
4427
4331
  onMediaAttached(event, data) {
4428
4332
  this.media = data.media;
4429
- this.media.addEventListener('timeupdate', this.onTimeupdate);
4333
+ this.media.addEventListener('timeupdate', this.timeupdateHandler);
4430
4334
  }
4431
4335
  onMediaDetaching() {
4432
4336
  if (this.media) {
4433
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4337
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4434
4338
  this.media = null;
4435
4339
  }
4436
4340
  }
@@ -4444,10 +4348,10 @@ class LatencyController {
4444
4348
  }) {
4445
4349
  this.levelDetails = details;
4446
4350
  if (details.advanced) {
4447
- this.onTimeupdate();
4351
+ this.timeupdate();
4448
4352
  }
4449
4353
  if (!details.live && this.media) {
4450
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4354
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4451
4355
  }
4452
4356
  }
4453
4357
  onError(event, data) {
@@ -4457,7 +4361,48 @@ class LatencyController {
4457
4361
  }
4458
4362
  this.stallCount++;
4459
4363
  if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
4460
- this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
4364
+ logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
4365
+ }
4366
+ }
4367
+ timeupdate() {
4368
+ const {
4369
+ media,
4370
+ levelDetails
4371
+ } = this;
4372
+ if (!media || !levelDetails) {
4373
+ return;
4374
+ }
4375
+ this.currentTime = media.currentTime;
4376
+ const latency = this.computeLatency();
4377
+ if (latency === null) {
4378
+ return;
4379
+ }
4380
+ this._latency = latency;
4381
+
4382
+ // Adapt playbackRate to meet target latency in low-latency mode
4383
+ const {
4384
+ lowLatencyMode,
4385
+ maxLiveSyncPlaybackRate
4386
+ } = this.config;
4387
+ if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4388
+ return;
4389
+ }
4390
+ const targetLatency = this.targetLatency;
4391
+ if (targetLatency === null) {
4392
+ return;
4393
+ }
4394
+ const distanceFromTarget = latency - targetLatency;
4395
+ // Only adjust playbackRate when within one target duration of targetLatency
4396
+ // and more than one second from under-buffering.
4397
+ // Playback further than one target duration from target can be considered DVR playback.
4398
+ const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4399
+ const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4400
+ if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4401
+ const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4402
+ const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4403
+ media.playbackRate = Math.min(max, Math.max(1, rate));
4404
+ } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4405
+ media.playbackRate = 1;
4461
4406
  }
4462
4407
  }
4463
4408
  estimateLiveEdge() {
@@ -5229,13 +5174,18 @@ var ErrorActionFlags = {
5229
5174
  MoveAllAlternatesMatchingHDCP: 2,
5230
5175
  SwitchToSDR: 4
5231
5176
  }; // Reserved for future use
5232
- class ErrorController extends Logger {
5177
+ class ErrorController {
5233
5178
  constructor(hls) {
5234
- super('error-controller', hls.logger);
5235
5179
  this.hls = void 0;
5236
5180
  this.playlistError = 0;
5237
5181
  this.penalizedRenditions = {};
5182
+ this.log = void 0;
5183
+ this.warn = void 0;
5184
+ this.error = void 0;
5238
5185
  this.hls = hls;
5186
+ this.log = logger.log.bind(logger, `[info]:`);
5187
+ this.warn = logger.warn.bind(logger, `[warning]:`);
5188
+ this.error = logger.error.bind(logger, `[error]:`);
5239
5189
  this.registerListeners();
5240
5190
  }
5241
5191
  registerListeners() {
@@ -5587,13 +5537,16 @@ class ErrorController extends Logger {
5587
5537
  }
5588
5538
  }
5589
5539
 
5590
- class BasePlaylistController extends Logger {
5540
+ class BasePlaylistController {
5591
5541
  constructor(hls, logPrefix) {
5592
- super(logPrefix, hls.logger);
5593
5542
  this.hls = void 0;
5594
5543
  this.timer = -1;
5595
5544
  this.requestScheduled = -1;
5596
5545
  this.canLoad = false;
5546
+ this.log = void 0;
5547
+ this.warn = void 0;
5548
+ this.log = logger.log.bind(logger, `${logPrefix}:`);
5549
+ this.warn = logger.warn.bind(logger, `${logPrefix}:`);
5597
5550
  this.hls = hls;
5598
5551
  }
5599
5552
  destroy() {
@@ -5626,7 +5579,7 @@ class BasePlaylistController extends Logger {
5626
5579
  try {
5627
5580
  uri = new self.URL(attr.URI, previous.url).href;
5628
5581
  } catch (error) {
5629
- this.warn(`Could not construct new URL for Rendition Report: ${error}`);
5582
+ logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
5630
5583
  uri = attr.URI || '';
5631
5584
  }
5632
5585
  // Use exact match. Otherwise, the last partial match, if any, will be used
@@ -5713,12 +5666,7 @@ class BasePlaylistController extends Logger {
5713
5666
  const cdnAge = lastAdvanced + details.ageHeader;
5714
5667
  let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
5715
5668
  if (currentGoal > 0) {
5716
- if (cdnAge > details.targetduration * 3) {
5717
- // Omit segment and part directives when the last response was more than 3 target durations ago,
5718
- this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
5719
- msn = undefined;
5720
- part = undefined;
5721
- } else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
5669
+ if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
5722
5670
  // If we attempted to get the next or latest playlist update, but currentGoal increased,
5723
5671
  // then we either can't catchup, or the "age" header cannot be trusted.
5724
5672
  this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
@@ -6177,9 +6125,8 @@ function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
6177
6125
  }, {});
6178
6126
  }
6179
6127
 
6180
- class AbrController extends Logger {
6128
+ class AbrController {
6181
6129
  constructor(_hls) {
6182
- super('abr', _hls.logger);
6183
6130
  this.hls = void 0;
6184
6131
  this.lastLevelLoadSec = 0;
6185
6132
  this.lastLoadedFragLevel = -1;
@@ -6293,7 +6240,7 @@ class AbrController extends Logger {
6293
6240
  this.resetEstimator(nextLoadLevelBitrate);
6294
6241
  }
6295
6242
  this.clearTimer();
6296
- this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6243
+ logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6297
6244
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
6298
6245
  Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
6299
6246
  Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
@@ -6313,7 +6260,7 @@ class AbrController extends Logger {
6313
6260
  }
6314
6261
  resetEstimator(abrEwmaDefaultEstimate) {
6315
6262
  if (abrEwmaDefaultEstimate) {
6316
- this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6263
+ logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6317
6264
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
6318
6265
  }
6319
6266
  this.firstSelection = -1;
@@ -6545,7 +6492,7 @@ class AbrController extends Logger {
6545
6492
  }
6546
6493
  const firstLevel = this.hls.firstLevel;
6547
6494
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
6548
- this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
6495
+ logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
6549
6496
  return clamped;
6550
6497
  }
6551
6498
  get forcedAutoLevel() {
@@ -6630,13 +6577,13 @@ class AbrController extends Logger {
6630
6577
  // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
6631
6578
  const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
6632
6579
  maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
6633
- this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
6580
+ logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
6634
6581
  // don't use conservative factor on bitrate test
6635
6582
  bwFactor = bwUpFactor = 1;
6636
6583
  }
6637
6584
  }
6638
6585
  const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
6639
- this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
6586
+ logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
6640
6587
  if (bestLevel > -1) {
6641
6588
  return bestLevel;
6642
6589
  }
@@ -6698,7 +6645,7 @@ class AbrController extends Logger {
6698
6645
  currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
6699
6646
  currentFrameRate = minFramerate;
6700
6647
  currentBw = Math.max(currentBw, minBitrate);
6701
- this.log(`picked start tier ${JSON.stringify(startTier)}`);
6648
+ logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
6702
6649
  } else {
6703
6650
  currentCodecSet = level == null ? void 0 : level.codecSet;
6704
6651
  currentVideoRange = level == null ? void 0 : level.videoRange;
@@ -6751,9 +6698,9 @@ class AbrController extends Logger {
6751
6698
  const forcedAutoLevel = this.forcedAutoLevel;
6752
6699
  if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
6753
6700
  if (levelsSkipped.length) {
6754
- 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}`);
6701
+ logger.trace(`[abr] Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
6755
6702
  }
6756
- 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}`);
6703
+ logger.info(`[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
6757
6704
  }
6758
6705
  if (firstSelection) {
6759
6706
  this.firstSelection = i;
@@ -6989,9 +6936,8 @@ class BufferOperationQueue {
6989
6936
  }
6990
6937
 
6991
6938
  const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
6992
- class BufferController extends Logger {
6939
+ class BufferController {
6993
6940
  constructor(hls) {
6994
- super('buffer-controller', hls.logger);
6995
6941
  // The level details used to determine duration, target-duration and live
6996
6942
  this.details = null;
6997
6943
  // cache the self generated object url to detect hijack of video tag
@@ -7021,6 +6967,9 @@ class BufferController extends Logger {
7021
6967
  this.tracks = {};
7022
6968
  this.pendingTracks = {};
7023
6969
  this.sourceBuffer = void 0;
6970
+ this.log = void 0;
6971
+ this.warn = void 0;
6972
+ this.error = void 0;
7024
6973
  this._onEndStreaming = event => {
7025
6974
  if (!this.hls) {
7026
6975
  return;
@@ -7066,11 +7015,15 @@ class BufferController extends Logger {
7066
7015
  _objectUrl
7067
7016
  } = this;
7068
7017
  if (mediaSrc !== _objectUrl) {
7069
- this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
7018
+ logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
7070
7019
  }
7071
7020
  };
7072
7021
  this.hls = hls;
7022
+ const logPrefix = '[buffer-controller]';
7073
7023
  this.appendSource = hls.config.preferManagedMediaSource;
7024
+ this.log = logger.log.bind(logger, logPrefix);
7025
+ this.warn = logger.warn.bind(logger, logPrefix);
7026
+ this.error = logger.error.bind(logger, logPrefix);
7074
7027
  this._initSourceBuffer();
7075
7028
  this.registerListeners();
7076
7029
  }
@@ -7083,12 +7036,6 @@ class BufferController extends Logger {
7083
7036
  this.lastMpegAudioChunk = null;
7084
7037
  // @ts-ignore
7085
7038
  this.hls = null;
7086
- // @ts-ignore
7087
- this._onMediaSourceOpen = this._onMediaSourceClose = null;
7088
- // @ts-ignore
7089
- this._onMediaSourceEnded = null;
7090
- // @ts-ignore
7091
- this._onStartStreaming = this._onEndStreaming = null;
7092
7039
  }
7093
7040
  registerListeners() {
7094
7041
  const {
@@ -7251,7 +7198,6 @@ class BufferController extends Logger {
7251
7198
  this.resetBuffer(type);
7252
7199
  });
7253
7200
  this._initSourceBuffer();
7254
- this.hls.resumeBuffering();
7255
7201
  }
7256
7202
  resetBuffer(type) {
7257
7203
  const sb = this.sourceBuffer[type];
@@ -8089,7 +8035,7 @@ class CapLevelController {
8089
8035
  const hls = this.hls;
8090
8036
  const maxLevel = this.getMaxLevel(levels.length - 1);
8091
8037
  if (maxLevel !== this.autoLevelCapping) {
8092
- hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
8038
+ logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
8093
8039
  }
8094
8040
  hls.autoLevelCapping = maxLevel;
8095
8041
  if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
@@ -8267,10 +8213,10 @@ class FPSController {
8267
8213
  totalDroppedFrames: droppedFrames
8268
8214
  });
8269
8215
  if (droppedFPS > 0) {
8270
- // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
8216
+ // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
8271
8217
  if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
8272
8218
  let currentLevel = hls.currentLevel;
8273
- hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
8219
+ logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
8274
8220
  if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
8275
8221
  currentLevel = currentLevel - 1;
8276
8222
  hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
@@ -8303,10 +8249,10 @@ class FPSController {
8303
8249
  }
8304
8250
 
8305
8251
  const PATHWAY_PENALTY_DURATION_MS = 300000;
8306
- class ContentSteeringController extends Logger {
8252
+ class ContentSteeringController {
8307
8253
  constructor(hls) {
8308
- super('content-steering', hls.logger);
8309
8254
  this.hls = void 0;
8255
+ this.log = void 0;
8310
8256
  this.loader = null;
8311
8257
  this.uri = null;
8312
8258
  this.pathwayId = '.';
@@ -8321,6 +8267,7 @@ class ContentSteeringController extends Logger {
8321
8267
  this.subtitleTracks = null;
8322
8268
  this.penalizedPathways = {};
8323
8269
  this.hls = hls;
8270
+ this.log = logger.log.bind(logger, `[content-steering]:`);
8324
8271
  this.registerListeners();
8325
8272
  }
8326
8273
  registerListeners() {
@@ -8444,7 +8391,7 @@ class ContentSteeringController extends Logger {
8444
8391
  errorAction.resolved = this.pathwayId !== errorPathway;
8445
8392
  }
8446
8393
  if (!errorAction.resolved) {
8447
- 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)}`);
8394
+ logger.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
8448
8395
  }
8449
8396
  }
8450
8397
  }
@@ -8615,7 +8562,7 @@ class ContentSteeringController extends Logger {
8615
8562
  onSuccess: (response, stats, context, networkDetails) => {
8616
8563
  this.log(`Loaded steering manifest: "${url}"`);
8617
8564
  const steeringData = response.data;
8618
- if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
8565
+ if (steeringData.VERSION !== 1) {
8619
8566
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
8620
8567
  return;
8621
8568
  }
@@ -9523,7 +9470,7 @@ const hlsDefaultConfig = _objectSpread2(_objectSpread2({
9523
9470
  });
9524
9471
  function timelineConfig() {
9525
9472
  return {
9526
- cueHandler: HevcVideoParser,
9473
+ cueHandler: Cues,
9527
9474
  // used by timeline-controller
9528
9475
  enableWebVTT: false,
9529
9476
  // used by timeline-controller
@@ -9554,7 +9501,7 @@ function timelineConfig() {
9554
9501
  /**
9555
9502
  * @ignore
9556
9503
  */
9557
- function mergeConfig(defaultConfig, userConfig, logger) {
9504
+ function mergeConfig(defaultConfig, userConfig) {
9558
9505
  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
9559
9506
  throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
9560
9507
  }
@@ -9624,7 +9571,7 @@ function deepCpy(obj) {
9624
9571
  /**
9625
9572
  * @ignore
9626
9573
  */
9627
- function enableStreamingMode(config, logger) {
9574
+ function enableStreamingMode(config) {
9628
9575
  const currentLoader = config.loader;
9629
9576
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
9630
9577
  // If a developer has configured their own loader, respect that choice
@@ -9641,9 +9588,10 @@ function enableStreamingMode(config, logger) {
9641
9588
  }
9642
9589
  }
9643
9590
 
9591
+ let chromeOrFirefox;
9644
9592
  class LevelController extends BasePlaylistController {
9645
9593
  constructor(hls, contentSteeringController) {
9646
- super(hls, 'level-controller');
9594
+ super(hls, '[level-controller]');
9647
9595
  this._levels = [];
9648
9596
  this._firstLevel = -1;
9649
9597
  this._maxAutoLevel = -1;
@@ -9714,15 +9662,23 @@ class LevelController extends BasePlaylistController {
9714
9662
  let videoCodecFound = false;
9715
9663
  let audioCodecFound = false;
9716
9664
  data.levels.forEach(levelParsed => {
9717
- var _videoCodec;
9665
+ var _audioCodec, _videoCodec;
9718
9666
  const attributes = levelParsed.attrs;
9667
+
9668
+ // erase audio codec info if browser does not support mp4a.40.34.
9669
+ // demuxer will autodetect codec and fallback to mpeg/audio
9719
9670
  let {
9720
9671
  audioCodec,
9721
9672
  videoCodec
9722
9673
  } = levelParsed;
9674
+ if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
9675
+ chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
9676
+ if (chromeOrFirefox) {
9677
+ levelParsed.audioCodec = audioCodec = undefined;
9678
+ }
9679
+ }
9723
9680
  if (audioCodec) {
9724
- // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
9725
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
9681
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
9726
9682
  }
9727
9683
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
9728
9684
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -10064,12 +10020,7 @@ class LevelController extends BasePlaylistController {
10064
10020
  if (curLevel.fragmentError === 0) {
10065
10021
  curLevel.loadError = 0;
10066
10022
  }
10067
- // Ignore matching details populated by loading a Media Playlist directly
10068
- let previousDetails = curLevel.details;
10069
- if (previousDetails === data.details && previousDetails.advanced) {
10070
- previousDetails = undefined;
10071
- }
10072
- this.playlistLoaded(level, data, previousDetails);
10023
+ this.playlistLoaded(level, data, curLevel.details);
10073
10024
  } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
10074
10025
  // received a delta playlist update that cannot be merged
10075
10026
  details.deltaUpdateFailed = true;
@@ -10855,8 +10806,8 @@ function createLoaderContext(frag, part = null) {
10855
10806
  var _frag$decryptdata;
10856
10807
  let byteRangeStart = start;
10857
10808
  let byteRangeEnd = end;
10858
- if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
10859
- // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
10809
+ if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
10810
+ // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
10860
10811
  // has the unencrypted size specified in the range.
10861
10812
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
10862
10813
  const fragmentLen = end - start;
@@ -10889,9 +10840,6 @@ function createGapLoadError(frag, part) {
10889
10840
  (part ? part : frag).stats.aborted = true;
10890
10841
  return new LoadError(errorData);
10891
10842
  }
10892
- function isMethodFullSegmentAesCbc(method) {
10893
- return method === 'AES-128' || method === 'AES-256';
10894
- }
10895
10843
  class LoadError extends Error {
10896
10844
  constructor(data) {
10897
10845
  super(data.error.message);
@@ -11037,8 +10985,6 @@ class KeyLoader {
11037
10985
  }
11038
10986
  return this.loadKeyEME(keyInfo, frag);
11039
10987
  case 'AES-128':
11040
- case 'AES-256':
11041
- case 'AES-256-CTR':
11042
10988
  return this.loadKeyHTTP(keyInfo, frag);
11043
10989
  default:
11044
10990
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -11174,9 +11120,8 @@ class KeyLoader {
11174
11120
  * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
11175
11121
  * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
11176
11122
  */
11177
- class TaskLoop extends Logger {
11178
- constructor(label, logger) {
11179
- super(label, logger);
11123
+ class TaskLoop {
11124
+ constructor() {
11180
11125
  this._boundTick = void 0;
11181
11126
  this._tickTimer = null;
11182
11127
  this._tickInterval = null;
@@ -11444,61 +11389,33 @@ function alignMediaPlaylistByPDT(details, refDetails) {
11444
11389
  }
11445
11390
 
11446
11391
  class AESCrypto {
11447
- constructor(subtle, iv, aesMode) {
11392
+ constructor(subtle, iv) {
11448
11393
  this.subtle = void 0;
11449
11394
  this.aesIV = void 0;
11450
- this.aesMode = void 0;
11451
11395
  this.subtle = subtle;
11452
11396
  this.aesIV = iv;
11453
- this.aesMode = aesMode;
11454
11397
  }
11455
11398
  decrypt(data, key) {
11456
- switch (this.aesMode) {
11457
- case DecrypterAesMode.cbc:
11458
- return this.subtle.decrypt({
11459
- name: 'AES-CBC',
11460
- iv: this.aesIV
11461
- }, key, data);
11462
- case DecrypterAesMode.ctr:
11463
- return this.subtle.decrypt({
11464
- name: 'AES-CTR',
11465
- counter: this.aesIV,
11466
- length: 64
11467
- },
11468
- //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
11469
- key, data);
11470
- default:
11471
- throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
11472
- }
11399
+ return this.subtle.decrypt({
11400
+ name: 'AES-CBC',
11401
+ iv: this.aesIV
11402
+ }, key, data);
11473
11403
  }
11474
11404
  }
11475
11405
 
11476
11406
  class FastAESKey {
11477
- constructor(subtle, key, aesMode) {
11407
+ constructor(subtle, key) {
11478
11408
  this.subtle = void 0;
11479
11409
  this.key = void 0;
11480
- this.aesMode = void 0;
11481
11410
  this.subtle = subtle;
11482
11411
  this.key = key;
11483
- this.aesMode = aesMode;
11484
11412
  }
11485
11413
  expandKey() {
11486
- const subtleAlgoName = getSubtleAlgoName(this.aesMode);
11487
11414
  return this.subtle.importKey('raw', this.key, {
11488
- name: subtleAlgoName
11415
+ name: 'AES-CBC'
11489
11416
  }, false, ['encrypt', 'decrypt']);
11490
11417
  }
11491
11418
  }
11492
- function getSubtleAlgoName(aesMode) {
11493
- switch (aesMode) {
11494
- case DecrypterAesMode.cbc:
11495
- return 'AES-CBC';
11496
- case DecrypterAesMode.ctr:
11497
- return 'AES-CTR';
11498
- default:
11499
- throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
11500
- }
11501
- }
11502
11419
 
11503
11420
  // PKCS7
11504
11421
  function removePadding(array) {
@@ -11748,8 +11665,7 @@ class Decrypter {
11748
11665
  this.currentIV = null;
11749
11666
  this.currentResult = null;
11750
11667
  this.useSoftware = void 0;
11751
- this.enableSoftwareAES = void 0;
11752
- this.enableSoftwareAES = config.enableSoftwareAES;
11668
+ this.useSoftware = config.enableSoftwareAES;
11753
11669
  this.removePKCS7Padding = removePKCS7Padding;
11754
11670
  // built in decryptor expects PKCS7 padding
11755
11671
  if (removePKCS7Padding) {
@@ -11762,7 +11678,9 @@ class Decrypter {
11762
11678
  /* no-op */
11763
11679
  }
11764
11680
  }
11765
- this.useSoftware = this.subtle === null;
11681
+ if (this.subtle === null) {
11682
+ this.useSoftware = true;
11683
+ }
11766
11684
  }
11767
11685
  destroy() {
11768
11686
  this.subtle = null;
@@ -11800,10 +11718,10 @@ class Decrypter {
11800
11718
  this.softwareDecrypter = null;
11801
11719
  }
11802
11720
  }
11803
- decrypt(data, key, iv, aesMode) {
11721
+ decrypt(data, key, iv) {
11804
11722
  if (this.useSoftware) {
11805
11723
  return new Promise((resolve, reject) => {
11806
- this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
11724
+ this.softwareDecrypt(new Uint8Array(data), key, iv);
11807
11725
  const decryptResult = this.flush();
11808
11726
  if (decryptResult) {
11809
11727
  resolve(decryptResult.buffer);
@@ -11812,21 +11730,17 @@ class Decrypter {
11812
11730
  }
11813
11731
  });
11814
11732
  }
11815
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
11733
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
11816
11734
  }
11817
11735
 
11818
11736
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
11819
11737
  // data is handled in the flush() call
11820
- softwareDecrypt(data, key, iv, aesMode) {
11738
+ softwareDecrypt(data, key, iv) {
11821
11739
  const {
11822
11740
  currentIV,
11823
11741
  currentResult,
11824
11742
  remainderData
11825
11743
  } = this;
11826
- if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
11827
- logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
11828
- return null;
11829
- }
11830
11744
  this.logOnce('JS AES decrypt');
11831
11745
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
11832
11746
  // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
@@ -11859,11 +11773,11 @@ class Decrypter {
11859
11773
  }
11860
11774
  return result;
11861
11775
  }
11862
- webCryptoDecrypt(data, key, iv, aesMode) {
11776
+ webCryptoDecrypt(data, key, iv) {
11863
11777
  const subtle = this.subtle;
11864
11778
  if (this.key !== key || !this.fastAesKey) {
11865
11779
  this.key = key;
11866
- this.fastAesKey = new FastAESKey(subtle, key, aesMode);
11780
+ this.fastAesKey = new FastAESKey(subtle, key);
11867
11781
  }
11868
11782
  return this.fastAesKey.expandKey().then(aesKey => {
11869
11783
  // decrypt using web crypto
@@ -11871,25 +11785,22 @@ class Decrypter {
11871
11785
  return Promise.reject(new Error('web crypto not initialized'));
11872
11786
  }
11873
11787
  this.logOnce('WebCrypto AES decrypt');
11874
- const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
11788
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv));
11875
11789
  return crypto.decrypt(data.buffer, aesKey);
11876
11790
  }).catch(err => {
11877
11791
  logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
11878
- return this.onWebCryptoError(data, key, iv, aesMode);
11792
+ return this.onWebCryptoError(data, key, iv);
11879
11793
  });
11880
11794
  }
11881
- onWebCryptoError(data, key, iv, aesMode) {
11882
- const enableSoftwareAES = this.enableSoftwareAES;
11883
- if (enableSoftwareAES) {
11884
- this.useSoftware = true;
11885
- this.logEnabled = true;
11886
- this.softwareDecrypt(data, key, iv, aesMode);
11887
- const decryptResult = this.flush();
11888
- if (decryptResult) {
11889
- return decryptResult.buffer;
11890
- }
11795
+ onWebCryptoError(data, key, iv) {
11796
+ this.useSoftware = true;
11797
+ this.logEnabled = true;
11798
+ this.softwareDecrypt(data, key, iv);
11799
+ const decryptResult = this.flush();
11800
+ if (decryptResult) {
11801
+ return decryptResult.buffer;
11891
11802
  }
11892
- throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
11803
+ throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
11893
11804
  }
11894
11805
  getValidChunk(data) {
11895
11806
  let currentChunk = data;
@@ -11940,7 +11851,7 @@ const State = {
11940
11851
  };
11941
11852
  class BaseStreamController extends TaskLoop {
11942
11853
  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
11943
- super(logPrefix, hls.logger);
11854
+ super();
11944
11855
  this.hls = void 0;
11945
11856
  this.fragPrevious = null;
11946
11857
  this.fragCurrent = null;
@@ -11965,98 +11876,22 @@ class BaseStreamController extends TaskLoop {
11965
11876
  this.startFragRequested = false;
11966
11877
  this.decrypter = void 0;
11967
11878
  this.initPTS = [];
11968
- this.buffering = true;
11969
- this.loadingParts = false;
11970
- this.onMediaSeeking = () => {
11971
- const {
11972
- config,
11973
- fragCurrent,
11974
- media,
11975
- mediaBuffer,
11976
- state
11977
- } = this;
11978
- const currentTime = media ? media.currentTime : 0;
11979
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
11980
- this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
11981
- if (this.state === State.ENDED) {
11982
- this.resetLoadingState();
11983
- } else if (fragCurrent) {
11984
- // Seeking while frag load is in progress
11985
- const tolerance = config.maxFragLookUpTolerance;
11986
- const fragStartOffset = fragCurrent.start - tolerance;
11987
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
11988
- // if seeking out of buffered range or into new one
11989
- if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
11990
- const pastFragment = currentTime > fragEndOffset;
11991
- // if the seek position is outside the current fragment range
11992
- if (currentTime < fragStartOffset || pastFragment) {
11993
- if (pastFragment && fragCurrent.loader) {
11994
- this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
11995
- fragCurrent.abortRequests();
11996
- this.resetLoadingState();
11997
- }
11998
- this.fragPrevious = null;
11999
- }
12000
- }
12001
- }
12002
- if (media) {
12003
- // Remove gap fragments
12004
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
12005
- this.lastCurrentTime = currentTime;
12006
- if (!this.loadingParts) {
12007
- const bufferEnd = Math.max(bufferInfo.end, currentTime);
12008
- const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
12009
- if (shouldLoadParts) {
12010
- this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
12011
- this.loadingParts = shouldLoadParts;
12012
- }
12013
- }
12014
- }
12015
-
12016
- // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
12017
- if (!this.loadedmetadata && !bufferInfo.len) {
12018
- this.nextLoadPosition = this.startPosition = currentTime;
12019
- }
12020
-
12021
- // Async tick to speed up processing
12022
- this.tickImmediate();
12023
- };
12024
- this.onMediaEnded = () => {
12025
- // reset startPosition and lastCurrentTime to restart playback @ stream beginning
12026
- this.startPosition = this.lastCurrentTime = 0;
12027
- if (this.playlistType === PlaylistLevelType.MAIN) {
12028
- this.hls.trigger(Events.MEDIA_ENDED, {
12029
- stalled: false
12030
- });
12031
- }
12032
- };
11879
+ this.onvseeking = null;
11880
+ this.onvended = null;
11881
+ this.logPrefix = '';
11882
+ this.log = void 0;
11883
+ this.warn = void 0;
12033
11884
  this.playlistType = playlistType;
11885
+ this.logPrefix = logPrefix;
11886
+ this.log = logger.log.bind(logger, `${logPrefix}:`);
11887
+ this.warn = logger.warn.bind(logger, `${logPrefix}:`);
12034
11888
  this.hls = hls;
12035
11889
  this.fragmentLoader = new FragmentLoader(hls.config);
12036
11890
  this.keyLoader = keyLoader;
12037
11891
  this.fragmentTracker = fragmentTracker;
12038
11892
  this.config = hls.config;
12039
11893
  this.decrypter = new Decrypter(hls.config);
12040
- }
12041
- registerListeners() {
12042
- const {
12043
- hls
12044
- } = this;
12045
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
12046
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
12047
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
12048
11894
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
12049
- hls.on(Events.ERROR, this.onError, this);
12050
- }
12051
- unregisterListeners() {
12052
- const {
12053
- hls
12054
- } = this;
12055
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
12056
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
12057
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
12058
- hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
12059
- hls.off(Events.ERROR, this.onError, this);
12060
11895
  }
12061
11896
  doTick() {
12062
11897
  this.onTickEnd();
@@ -12080,12 +11915,6 @@ class BaseStreamController extends TaskLoop {
12080
11915
  this.clearNextTick();
12081
11916
  this.state = State.STOPPED;
12082
11917
  }
12083
- pauseBuffering() {
12084
- this.buffering = false;
12085
- }
12086
- resumeBuffering() {
12087
- this.buffering = true;
12088
- }
12089
11918
  _streamEnded(bufferInfo, levelDetails) {
12090
11919
  // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
12091
11920
  // of nothing loading/loaded return false
@@ -12116,8 +11945,10 @@ class BaseStreamController extends TaskLoop {
12116
11945
  }
12117
11946
  onMediaAttached(event, data) {
12118
11947
  const media = this.media = this.mediaBuffer = data.media;
12119
- media.addEventListener('seeking', this.onMediaSeeking);
12120
- media.addEventListener('ended', this.onMediaEnded);
11948
+ this.onvseeking = this.onMediaSeeking.bind(this);
11949
+ this.onvended = this.onMediaEnded.bind(this);
11950
+ media.addEventListener('seeking', this.onvseeking);
11951
+ media.addEventListener('ended', this.onvended);
12121
11952
  const config = this.config;
12122
11953
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
12123
11954
  this.startLoad(config.startPosition);
@@ -12131,9 +11962,10 @@ class BaseStreamController extends TaskLoop {
12131
11962
  }
12132
11963
 
12133
11964
  // remove video listeners
12134
- if (media) {
12135
- media.removeEventListener('seeking', this.onMediaSeeking);
12136
- media.removeEventListener('ended', this.onMediaEnded);
11965
+ if (media && this.onvseeking && this.onvended) {
11966
+ media.removeEventListener('seeking', this.onvseeking);
11967
+ media.removeEventListener('ended', this.onvended);
11968
+ this.onvseeking = this.onvended = null;
12137
11969
  }
12138
11970
  if (this.keyLoader) {
12139
11971
  this.keyLoader.detach();
@@ -12143,8 +11975,56 @@ class BaseStreamController extends TaskLoop {
12143
11975
  this.fragmentTracker.removeAllFragments();
12144
11976
  this.stopLoad();
12145
11977
  }
12146
- onManifestLoading() {}
12147
- onError(event, data) {}
11978
+ onMediaSeeking() {
11979
+ const {
11980
+ config,
11981
+ fragCurrent,
11982
+ media,
11983
+ mediaBuffer,
11984
+ state
11985
+ } = this;
11986
+ const currentTime = media ? media.currentTime : 0;
11987
+ const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
11988
+ this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
11989
+ if (this.state === State.ENDED) {
11990
+ this.resetLoadingState();
11991
+ } else if (fragCurrent) {
11992
+ // Seeking while frag load is in progress
11993
+ const tolerance = config.maxFragLookUpTolerance;
11994
+ const fragStartOffset = fragCurrent.start - tolerance;
11995
+ const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
11996
+ // if seeking out of buffered range or into new one
11997
+ if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
11998
+ const pastFragment = currentTime > fragEndOffset;
11999
+ // if the seek position is outside the current fragment range
12000
+ if (currentTime < fragStartOffset || pastFragment) {
12001
+ if (pastFragment && fragCurrent.loader) {
12002
+ this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
12003
+ fragCurrent.abortRequests();
12004
+ this.resetLoadingState();
12005
+ }
12006
+ this.fragPrevious = null;
12007
+ }
12008
+ }
12009
+ }
12010
+ if (media) {
12011
+ // Remove gap fragments
12012
+ this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
12013
+ this.lastCurrentTime = currentTime;
12014
+ }
12015
+
12016
+ // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
12017
+ if (!this.loadedmetadata && !bufferInfo.len) {
12018
+ this.nextLoadPosition = this.startPosition = currentTime;
12019
+ }
12020
+
12021
+ // Async tick to speed up processing
12022
+ this.tickImmediate();
12023
+ }
12024
+ onMediaEnded() {
12025
+ // reset startPosition and lastCurrentTime to restart playback @ stream beginning
12026
+ this.startPosition = this.lastCurrentTime = 0;
12027
+ }
12148
12028
  onManifestLoaded(event, data) {
12149
12029
  this.startTimeOffset = data.startTimeOffset;
12150
12030
  this.initPTS = [];
@@ -12154,7 +12034,7 @@ class BaseStreamController extends TaskLoop {
12154
12034
  this.stopLoad();
12155
12035
  super.onHandlerDestroying();
12156
12036
  // @ts-ignore
12157
- this.hls = this.onMediaSeeking = this.onMediaEnded = null;
12037
+ this.hls = null;
12158
12038
  }
12159
12039
  onHandlerDestroyed() {
12160
12040
  this.state = State.STOPPED;
@@ -12285,10 +12165,10 @@ class BaseStreamController extends TaskLoop {
12285
12165
  const decryptData = frag.decryptdata;
12286
12166
 
12287
12167
  // check to see if the payload needs to be decrypted
12288
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
12168
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
12289
12169
  const startTime = self.performance.now();
12290
12170
  // decrypt init segment data
12291
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
12171
+ return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
12292
12172
  hls.trigger(Events.ERROR, {
12293
12173
  type: ErrorTypes.MEDIA_ERROR,
12294
12174
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -12400,7 +12280,7 @@ class BaseStreamController extends TaskLoop {
12400
12280
  }
12401
12281
  let keyLoadingPromise = null;
12402
12282
  if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
12403
- this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
12283
+ this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
12404
12284
  this.state = State.KEY_LOADING;
12405
12285
  this.fragCurrent = frag;
12406
12286
  keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
@@ -12421,16 +12301,8 @@ class BaseStreamController extends TaskLoop {
12421
12301
  } else if (!frag.encrypted && details.encryptedFragments.length) {
12422
12302
  this.keyLoader.loadClear(frag, details.encryptedFragments);
12423
12303
  }
12424
- const fragPrevious = this.fragPrevious;
12425
- if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
12426
- const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
12427
- if (shouldLoadParts !== this.loadingParts) {
12428
- this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
12429
- this.loadingParts = shouldLoadParts;
12430
- }
12431
- }
12432
12304
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
12433
- if (this.loadingParts && frag.sn !== 'initSegment') {
12305
+ if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
12434
12306
  const partList = details.partList;
12435
12307
  if (partList && progressCallback) {
12436
12308
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -12439,7 +12311,7 @@ class BaseStreamController extends TaskLoop {
12439
12311
  const partIndex = this.getNextPart(partList, frag, targetBufferTime);
12440
12312
  if (partIndex > -1) {
12441
12313
  const part = partList[partIndex];
12442
- 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))}`);
12314
+ this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
12443
12315
  this.nextLoadPosition = part.start + part.duration;
12444
12316
  this.state = State.FRAG_LOADING;
12445
12317
  let _result;
@@ -12468,14 +12340,7 @@ class BaseStreamController extends TaskLoop {
12468
12340
  }
12469
12341
  }
12470
12342
  }
12471
- if (frag.sn !== 'initSegment' && this.loadingParts) {
12472
- this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
12473
- this.loadingParts = false;
12474
- } else if (!frag.url) {
12475
- // Selected fragment hint for part but not loading parts
12476
- return Promise.resolve(null);
12477
- }
12478
- 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))}`);
12343
+ this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
12479
12344
  // Don't update nextLoadPosition for fragments which are not buffered
12480
12345
  if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
12481
12346
  this.nextLoadPosition = frag.start + frag.duration;
@@ -12573,36 +12438,8 @@ class BaseStreamController extends TaskLoop {
12573
12438
  if (part) {
12574
12439
  part.stats.parsing.end = now;
12575
12440
  }
12576
- // See if part loading should be disabled/enabled based on buffer and playback position.
12577
- if (frag.sn !== 'initSegment') {
12578
- const levelDetails = this.getLevelDetails();
12579
- const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
12580
- const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
12581
- if (shouldLoadParts !== this.loadingParts) {
12582
- this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
12583
- this.loadingParts = shouldLoadParts;
12584
- }
12585
- }
12586
12441
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
12587
12442
  }
12588
- shouldLoadParts(details, bufferEnd) {
12589
- if (this.config.lowLatencyMode) {
12590
- if (!details) {
12591
- return this.loadingParts;
12592
- }
12593
- if (details != null && details.partList) {
12594
- var _details$fragmentHint;
12595
- // Buffer must be ahead of first part + duration of parts after last segment
12596
- // and playback must be at or past segment adjacent to part list
12597
- const firstPart = details.partList[0];
12598
- const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
12599
- if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
12600
- return true;
12601
- }
12602
- }
12603
- }
12604
- return false;
12605
- }
12606
12443
  getCurrentContext(chunkMeta) {
12607
12444
  const {
12608
12445
  levels,
@@ -12751,8 +12588,7 @@ class BaseStreamController extends TaskLoop {
12751
12588
  config
12752
12589
  } = this;
12753
12590
  const start = fragments[0].start;
12754
- const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
12755
- let frag = null;
12591
+ let frag;
12756
12592
  if (levelDetails.live) {
12757
12593
  const initialLiveManifestSize = config.initialLiveManifestSize;
12758
12594
  if (fragLen < initialLiveManifestSize) {
@@ -12764,10 +12600,6 @@ class BaseStreamController extends TaskLoop {
12764
12600
  // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
12765
12601
  // we get the fragment matching that start time
12766
12602
  if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
12767
- if (canLoadParts && !this.loadingParts) {
12768
- this.log(`LL-Part loading ON for initial live fragment`);
12769
- this.loadingParts = true;
12770
- }
12771
12603
  frag = this.getInitialLiveFragment(levelDetails, fragments);
12772
12604
  this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
12773
12605
  }
@@ -12778,7 +12610,7 @@ class BaseStreamController extends TaskLoop {
12778
12610
 
12779
12611
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
12780
12612
  if (!frag) {
12781
- const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
12613
+ const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
12782
12614
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
12783
12615
  }
12784
12616
  return this.mapToInitFragWhenRequired(frag);
@@ -12900,7 +12732,7 @@ class BaseStreamController extends TaskLoop {
12900
12732
  } = levelDetails;
12901
12733
  const tolerance = config.maxFragLookUpTolerance;
12902
12734
  const partList = levelDetails.partList;
12903
- const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
12735
+ const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
12904
12736
  if (loadingParts && fragmentHint && !this.bitrateTest) {
12905
12737
  // Include incomplete fragment with parts at end
12906
12738
  fragments = fragments.concat(fragmentHint);
@@ -13093,7 +12925,7 @@ class BaseStreamController extends TaskLoop {
13093
12925
  errorAction.resolved = true;
13094
12926
  }
13095
12927
  } else {
13096
- this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
12928
+ logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
13097
12929
  return;
13098
12930
  }
13099
12931
  } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
@@ -13488,7 +13320,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
13488
13320
  */
13489
13321
  function getAudioConfig(observer, data, offset, audioCodec) {
13490
13322
  let adtsObjectType;
13491
- let originalAdtsObjectType;
13492
13323
  let adtsExtensionSamplingIndex;
13493
13324
  let adtsChannelConfig;
13494
13325
  let config;
@@ -13496,7 +13327,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13496
13327
  const manifestCodec = audioCodec;
13497
13328
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
13498
13329
  // byte 2
13499
- adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
13330
+ adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
13500
13331
  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
13501
13332
  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
13502
13333
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
@@ -13513,8 +13344,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13513
13344
  // byte 3
13514
13345
  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
13515
13346
  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
13516
- // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
13517
- if (/firefox|palemoon/i.test(userAgent)) {
13347
+ // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
13348
+ if (/firefox/i.test(userAgent)) {
13518
13349
  if (adtsSamplingIndex >= 6) {
13519
13350
  adtsObjectType = 5;
13520
13351
  config = new Array(4);
@@ -13608,7 +13439,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13608
13439
  samplerate: adtsSamplingRates[adtsSamplingIndex],
13609
13440
  channelCount: adtsChannelConfig,
13610
13441
  codec: 'mp4a.40.' + adtsObjectType,
13611
- parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
13612
13442
  manifestCodec
13613
13443
  };
13614
13444
  }
@@ -13663,8 +13493,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
13663
13493
  track.channelCount = config.channelCount;
13664
13494
  track.codec = config.codec;
13665
13495
  track.manifestCodec = config.manifestCodec;
13666
- track.parsedCodec = config.parsedCodec;
13667
- logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
13496
+ logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
13668
13497
  }
13669
13498
  }
13670
13499
  function getFrameDuration(samplerate) {
@@ -14142,110 +13971,6 @@ class BaseVideoParser {
14142
13971
  logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
14143
13972
  }
14144
13973
  }
14145
- parseNALu(track, array) {
14146
- const len = array.byteLength;
14147
- let state = track.naluState || 0;
14148
- const lastState = state;
14149
- const units = [];
14150
- let i = 0;
14151
- let value;
14152
- let overflow;
14153
- let unitType;
14154
- let lastUnitStart = -1;
14155
- let lastUnitType = 0;
14156
- // logger.log('PES:' + Hex.hexDump(array));
14157
-
14158
- if (state === -1) {
14159
- // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
14160
- lastUnitStart = 0;
14161
- // NALu type is value read from offset 0
14162
- lastUnitType = this.getNALuType(array, 0);
14163
- state = 0;
14164
- i = 1;
14165
- }
14166
- while (i < len) {
14167
- value = array[i++];
14168
- // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
14169
- if (!state) {
14170
- state = value ? 0 : 1;
14171
- continue;
14172
- }
14173
- if (state === 1) {
14174
- state = value ? 0 : 2;
14175
- continue;
14176
- }
14177
- // here we have state either equal to 2 or 3
14178
- if (!value) {
14179
- state = 3;
14180
- } else if (value === 1) {
14181
- overflow = i - state - 1;
14182
- if (lastUnitStart >= 0) {
14183
- const unit = {
14184
- data: array.subarray(lastUnitStart, overflow),
14185
- type: lastUnitType
14186
- };
14187
- // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
14188
- units.push(unit);
14189
- } else {
14190
- // lastUnitStart is undefined => this is the first start code found in this PES packet
14191
- // first check if start code delimiter is overlapping between 2 PES packets,
14192
- // ie it started in last packet (lastState not zero)
14193
- // and ended at the beginning of this PES packet (i <= 4 - lastState)
14194
- const lastUnit = this.getLastNalUnit(track.samples);
14195
- if (lastUnit) {
14196
- if (lastState && i <= 4 - lastState) {
14197
- // start delimiter overlapping between PES packets
14198
- // strip start delimiter bytes from the end of last NAL unit
14199
- // check if lastUnit had a state different from zero
14200
- if (lastUnit.state) {
14201
- // strip last bytes
14202
- lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
14203
- }
14204
- }
14205
- // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
14206
-
14207
- if (overflow > 0) {
14208
- // logger.log('first NALU found with overflow:' + overflow);
14209
- lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
14210
- lastUnit.state = 0;
14211
- }
14212
- }
14213
- }
14214
- // check if we can read unit type
14215
- if (i < len) {
14216
- unitType = this.getNALuType(array, i);
14217
- // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
14218
- lastUnitStart = i;
14219
- lastUnitType = unitType;
14220
- state = 0;
14221
- } else {
14222
- // not enough byte to read unit type. let's read it on next PES parsing
14223
- state = -1;
14224
- }
14225
- } else {
14226
- state = 0;
14227
- }
14228
- }
14229
- if (lastUnitStart >= 0 && state >= 0) {
14230
- const unit = {
14231
- data: array.subarray(lastUnitStart, len),
14232
- type: lastUnitType,
14233
- state: state
14234
- };
14235
- units.push(unit);
14236
- // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
14237
- }
14238
- // no NALu found
14239
- if (units.length === 0) {
14240
- // append pes.data to previous NAL unit
14241
- const lastUnit = this.getLastNalUnit(track.samples);
14242
- if (lastUnit) {
14243
- lastUnit.data = appendUint8Array(lastUnit.data, array);
14244
- }
14245
- }
14246
- track.naluState = state;
14247
- return units;
14248
- }
14249
13974
  }
14250
13975
 
14251
13976
  /**
@@ -14388,11 +14113,194 @@ class ExpGolomb {
14388
14113
  readUInt() {
14389
14114
  return this.readBits(32);
14390
14115
  }
14116
+
14117
+ /**
14118
+ * Advance the ExpGolomb decoder past a scaling list. The scaling
14119
+ * list is optionally transmitted as part of a sequence parameter
14120
+ * set and is not relevant to transmuxing.
14121
+ * @param count the number of entries in this scaling list
14122
+ * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
14123
+ */
14124
+ skipScalingList(count) {
14125
+ let lastScale = 8;
14126
+ let nextScale = 8;
14127
+ let deltaScale;
14128
+ for (let j = 0; j < count; j++) {
14129
+ if (nextScale !== 0) {
14130
+ deltaScale = this.readEG();
14131
+ nextScale = (lastScale + deltaScale + 256) % 256;
14132
+ }
14133
+ lastScale = nextScale === 0 ? lastScale : nextScale;
14134
+ }
14135
+ }
14136
+
14137
+ /**
14138
+ * Read a sequence parameter set and return some interesting video
14139
+ * properties. A sequence parameter set is the H264 metadata that
14140
+ * describes the properties of upcoming video frames.
14141
+ * @returns an object with configuration parsed from the
14142
+ * sequence parameter set, including the dimensions of the
14143
+ * associated video frames.
14144
+ */
14145
+ readSPS() {
14146
+ let frameCropLeftOffset = 0;
14147
+ let frameCropRightOffset = 0;
14148
+ let frameCropTopOffset = 0;
14149
+ let frameCropBottomOffset = 0;
14150
+ let numRefFramesInPicOrderCntCycle;
14151
+ let scalingListCount;
14152
+ let i;
14153
+ const readUByte = this.readUByte.bind(this);
14154
+ const readBits = this.readBits.bind(this);
14155
+ const readUEG = this.readUEG.bind(this);
14156
+ const readBoolean = this.readBoolean.bind(this);
14157
+ const skipBits = this.skipBits.bind(this);
14158
+ const skipEG = this.skipEG.bind(this);
14159
+ const skipUEG = this.skipUEG.bind(this);
14160
+ const skipScalingList = this.skipScalingList.bind(this);
14161
+ readUByte();
14162
+ const profileIdc = readUByte(); // profile_idc
14163
+ readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
14164
+ skipBits(3); // reserved_zero_3bits u(3),
14165
+ readUByte(); // level_idc u(8)
14166
+ skipUEG(); // seq_parameter_set_id
14167
+ // some profiles have more optional data we don't need
14168
+ if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
14169
+ const chromaFormatIdc = readUEG();
14170
+ if (chromaFormatIdc === 3) {
14171
+ skipBits(1);
14172
+ } // separate_colour_plane_flag
14173
+
14174
+ skipUEG(); // bit_depth_luma_minus8
14175
+ skipUEG(); // bit_depth_chroma_minus8
14176
+ skipBits(1); // qpprime_y_zero_transform_bypass_flag
14177
+ if (readBoolean()) {
14178
+ // seq_scaling_matrix_present_flag
14179
+ scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
14180
+ for (i = 0; i < scalingListCount; i++) {
14181
+ if (readBoolean()) {
14182
+ // seq_scaling_list_present_flag[ i ]
14183
+ if (i < 6) {
14184
+ skipScalingList(16);
14185
+ } else {
14186
+ skipScalingList(64);
14187
+ }
14188
+ }
14189
+ }
14190
+ }
14191
+ }
14192
+ skipUEG(); // log2_max_frame_num_minus4
14193
+ const picOrderCntType = readUEG();
14194
+ if (picOrderCntType === 0) {
14195
+ readUEG(); // log2_max_pic_order_cnt_lsb_minus4
14196
+ } else if (picOrderCntType === 1) {
14197
+ skipBits(1); // delta_pic_order_always_zero_flag
14198
+ skipEG(); // offset_for_non_ref_pic
14199
+ skipEG(); // offset_for_top_to_bottom_field
14200
+ numRefFramesInPicOrderCntCycle = readUEG();
14201
+ for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
14202
+ skipEG();
14203
+ } // offset_for_ref_frame[ i ]
14204
+ }
14205
+ skipUEG(); // max_num_ref_frames
14206
+ skipBits(1); // gaps_in_frame_num_value_allowed_flag
14207
+ const picWidthInMbsMinus1 = readUEG();
14208
+ const picHeightInMapUnitsMinus1 = readUEG();
14209
+ const frameMbsOnlyFlag = readBits(1);
14210
+ if (frameMbsOnlyFlag === 0) {
14211
+ skipBits(1);
14212
+ } // mb_adaptive_frame_field_flag
14213
+
14214
+ skipBits(1); // direct_8x8_inference_flag
14215
+ if (readBoolean()) {
14216
+ // frame_cropping_flag
14217
+ frameCropLeftOffset = readUEG();
14218
+ frameCropRightOffset = readUEG();
14219
+ frameCropTopOffset = readUEG();
14220
+ frameCropBottomOffset = readUEG();
14221
+ }
14222
+ let pixelRatio = [1, 1];
14223
+ if (readBoolean()) {
14224
+ // vui_parameters_present_flag
14225
+ if (readBoolean()) {
14226
+ // aspect_ratio_info_present_flag
14227
+ const aspectRatioIdc = readUByte();
14228
+ switch (aspectRatioIdc) {
14229
+ case 1:
14230
+ pixelRatio = [1, 1];
14231
+ break;
14232
+ case 2:
14233
+ pixelRatio = [12, 11];
14234
+ break;
14235
+ case 3:
14236
+ pixelRatio = [10, 11];
14237
+ break;
14238
+ case 4:
14239
+ pixelRatio = [16, 11];
14240
+ break;
14241
+ case 5:
14242
+ pixelRatio = [40, 33];
14243
+ break;
14244
+ case 6:
14245
+ pixelRatio = [24, 11];
14246
+ break;
14247
+ case 7:
14248
+ pixelRatio = [20, 11];
14249
+ break;
14250
+ case 8:
14251
+ pixelRatio = [32, 11];
14252
+ break;
14253
+ case 9:
14254
+ pixelRatio = [80, 33];
14255
+ break;
14256
+ case 10:
14257
+ pixelRatio = [18, 11];
14258
+ break;
14259
+ case 11:
14260
+ pixelRatio = [15, 11];
14261
+ break;
14262
+ case 12:
14263
+ pixelRatio = [64, 33];
14264
+ break;
14265
+ case 13:
14266
+ pixelRatio = [160, 99];
14267
+ break;
14268
+ case 14:
14269
+ pixelRatio = [4, 3];
14270
+ break;
14271
+ case 15:
14272
+ pixelRatio = [3, 2];
14273
+ break;
14274
+ case 16:
14275
+ pixelRatio = [2, 1];
14276
+ break;
14277
+ case 255:
14278
+ {
14279
+ pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
14280
+ break;
14281
+ }
14282
+ }
14283
+ }
14284
+ }
14285
+ return {
14286
+ width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
14287
+ height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
14288
+ pixelRatio: pixelRatio
14289
+ };
14290
+ }
14291
+ readSliceType() {
14292
+ // skip NALu type
14293
+ this.readUByte();
14294
+ // discard first_mb_in_slice
14295
+ this.readUEG();
14296
+ // return slice_type
14297
+ return this.readUEG();
14298
+ }
14391
14299
  }
14392
14300
 
14393
14301
  class AvcVideoParser extends BaseVideoParser {
14394
- parsePES(track, textTrack, pes, last, duration) {
14395
- const units = this.parseNALu(track, pes.data);
14302
+ parseAVCPES(track, textTrack, pes, last, duration) {
14303
+ const units = this.parseAVCNALu(track, pes.data);
14396
14304
  let VideoSample = this.VideoSample;
14397
14305
  let push;
14398
14306
  let spsfound = false;
@@ -14417,7 +14325,7 @@ class AvcVideoParser extends BaseVideoParser {
14417
14325
  // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
14418
14326
  if (spsfound && data.length > 4) {
14419
14327
  // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
14420
- const sliceType = this.readSliceType(data);
14328
+ const sliceType = new ExpGolomb(data).readSliceType();
14421
14329
  // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
14422
14330
  // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
14423
14331
  // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
@@ -14471,7 +14379,8 @@ class AvcVideoParser extends BaseVideoParser {
14471
14379
  push = true;
14472
14380
  spsfound = true;
14473
14381
  const sps = unit.data;
14474
- const config = this.readSPS(sps);
14382
+ const expGolombDecoder = new ExpGolomb(sps);
14383
+ const config = expGolombDecoder.readSPS();
14475
14384
  if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) {
14476
14385
  track.width = config.width;
14477
14386
  track.height = config.height;
@@ -14527,192 +14436,109 @@ class AvcVideoParser extends BaseVideoParser {
14527
14436
  this.VideoSample = null;
14528
14437
  }
14529
14438
  }
14530
- getNALuType(data, offset) {
14531
- return data[offset] & 0x1f;
14532
- }
14533
- readSliceType(data) {
14534
- const eg = new ExpGolomb(data);
14535
- // skip NALu type
14536
- eg.readUByte();
14537
- // discard first_mb_in_slice
14538
- eg.readUEG();
14539
- // return slice_type
14540
- return eg.readUEG();
14541
- }
14439
+ parseAVCNALu(track, array) {
14440
+ const len = array.byteLength;
14441
+ let state = track.naluState || 0;
14442
+ const lastState = state;
14443
+ const units = [];
14444
+ let i = 0;
14445
+ let value;
14446
+ let overflow;
14447
+ let unitType;
14448
+ let lastUnitStart = -1;
14449
+ let lastUnitType = 0;
14450
+ // logger.log('PES:' + Hex.hexDump(array));
14542
14451
 
14543
- /**
14544
- * The scaling list is optionally transmitted as part of a sequence parameter
14545
- * set and is not relevant to transmuxing.
14546
- * @param count the number of entries in this scaling list
14547
- * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
14548
- */
14549
- skipScalingList(count, reader) {
14550
- let lastScale = 8;
14551
- let nextScale = 8;
14552
- let deltaScale;
14553
- for (let j = 0; j < count; j++) {
14554
- if (nextScale !== 0) {
14555
- deltaScale = reader.readEG();
14556
- nextScale = (lastScale + deltaScale + 256) % 256;
14557
- }
14558
- lastScale = nextScale === 0 ? lastScale : nextScale;
14452
+ if (state === -1) {
14453
+ // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
14454
+ lastUnitStart = 0;
14455
+ // NALu type is value read from offset 0
14456
+ lastUnitType = array[0] & 0x1f;
14457
+ state = 0;
14458
+ i = 1;
14559
14459
  }
14560
- }
14561
-
14562
- /**
14563
- * Read a sequence parameter set and return some interesting video
14564
- * properties. A sequence parameter set is the H264 metadata that
14565
- * describes the properties of upcoming video frames.
14566
- * @returns an object with configuration parsed from the
14567
- * sequence parameter set, including the dimensions of the
14568
- * associated video frames.
14569
- */
14570
- readSPS(sps) {
14571
- const eg = new ExpGolomb(sps);
14572
- let frameCropLeftOffset = 0;
14573
- let frameCropRightOffset = 0;
14574
- let frameCropTopOffset = 0;
14575
- let frameCropBottomOffset = 0;
14576
- let numRefFramesInPicOrderCntCycle;
14577
- let scalingListCount;
14578
- let i;
14579
- const readUByte = eg.readUByte.bind(eg);
14580
- const readBits = eg.readBits.bind(eg);
14581
- const readUEG = eg.readUEG.bind(eg);
14582
- const readBoolean = eg.readBoolean.bind(eg);
14583
- const skipBits = eg.skipBits.bind(eg);
14584
- const skipEG = eg.skipEG.bind(eg);
14585
- const skipUEG = eg.skipUEG.bind(eg);
14586
- const skipScalingList = this.skipScalingList.bind(this);
14587
- readUByte();
14588
- const profileIdc = readUByte(); // profile_idc
14589
- readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
14590
- skipBits(3); // reserved_zero_3bits u(3),
14591
- readUByte(); // level_idc u(8)
14592
- skipUEG(); // seq_parameter_set_id
14593
- // some profiles have more optional data we don't need
14594
- if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
14595
- const chromaFormatIdc = readUEG();
14596
- if (chromaFormatIdc === 3) {
14597
- skipBits(1);
14598
- } // separate_colour_plane_flag
14599
-
14600
- skipUEG(); // bit_depth_luma_minus8
14601
- skipUEG(); // bit_depth_chroma_minus8
14602
- skipBits(1); // qpprime_y_zero_transform_bypass_flag
14603
- if (readBoolean()) {
14604
- // seq_scaling_matrix_present_flag
14605
- scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
14606
- for (i = 0; i < scalingListCount; i++) {
14607
- if (readBoolean()) {
14608
- // seq_scaling_list_present_flag[ i ]
14609
- if (i < 6) {
14610
- skipScalingList(16, eg);
14611
- } else {
14612
- skipScalingList(64, eg);
14460
+ while (i < len) {
14461
+ value = array[i++];
14462
+ // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
14463
+ if (!state) {
14464
+ state = value ? 0 : 1;
14465
+ continue;
14466
+ }
14467
+ if (state === 1) {
14468
+ state = value ? 0 : 2;
14469
+ continue;
14470
+ }
14471
+ // here we have state either equal to 2 or 3
14472
+ if (!value) {
14473
+ state = 3;
14474
+ } else if (value === 1) {
14475
+ overflow = i - state - 1;
14476
+ if (lastUnitStart >= 0) {
14477
+ const unit = {
14478
+ data: array.subarray(lastUnitStart, overflow),
14479
+ type: lastUnitType
14480
+ };
14481
+ // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
14482
+ units.push(unit);
14483
+ } else {
14484
+ // lastUnitStart is undefined => this is the first start code found in this PES packet
14485
+ // first check if start code delimiter is overlapping between 2 PES packets,
14486
+ // ie it started in last packet (lastState not zero)
14487
+ // and ended at the beginning of this PES packet (i <= 4 - lastState)
14488
+ const lastUnit = this.getLastNalUnit(track.samples);
14489
+ if (lastUnit) {
14490
+ if (lastState && i <= 4 - lastState) {
14491
+ // start delimiter overlapping between PES packets
14492
+ // strip start delimiter bytes from the end of last NAL unit
14493
+ // check if lastUnit had a state different from zero
14494
+ if (lastUnit.state) {
14495
+ // strip last bytes
14496
+ lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
14497
+ }
14498
+ }
14499
+ // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
14500
+
14501
+ if (overflow > 0) {
14502
+ // logger.log('first NALU found with overflow:' + overflow);
14503
+ lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
14504
+ lastUnit.state = 0;
14613
14505
  }
14614
14506
  }
14615
14507
  }
14508
+ // check if we can read unit type
14509
+ if (i < len) {
14510
+ unitType = array[i] & 0x1f;
14511
+ // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
14512
+ lastUnitStart = i;
14513
+ lastUnitType = unitType;
14514
+ state = 0;
14515
+ } else {
14516
+ // not enough byte to read unit type. let's read it on next PES parsing
14517
+ state = -1;
14518
+ }
14519
+ } else {
14520
+ state = 0;
14616
14521
  }
14617
14522
  }
14618
- skipUEG(); // log2_max_frame_num_minus4
14619
- const picOrderCntType = readUEG();
14620
- if (picOrderCntType === 0) {
14621
- readUEG(); // log2_max_pic_order_cnt_lsb_minus4
14622
- } else if (picOrderCntType === 1) {
14623
- skipBits(1); // delta_pic_order_always_zero_flag
14624
- skipEG(); // offset_for_non_ref_pic
14625
- skipEG(); // offset_for_top_to_bottom_field
14626
- numRefFramesInPicOrderCntCycle = readUEG();
14627
- for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
14628
- skipEG();
14629
- } // offset_for_ref_frame[ i ]
14630
- }
14631
- skipUEG(); // max_num_ref_frames
14632
- skipBits(1); // gaps_in_frame_num_value_allowed_flag
14633
- const picWidthInMbsMinus1 = readUEG();
14634
- const picHeightInMapUnitsMinus1 = readUEG();
14635
- const frameMbsOnlyFlag = readBits(1);
14636
- if (frameMbsOnlyFlag === 0) {
14637
- skipBits(1);
14638
- } // mb_adaptive_frame_field_flag
14639
-
14640
- skipBits(1); // direct_8x8_inference_flag
14641
- if (readBoolean()) {
14642
- // frame_cropping_flag
14643
- frameCropLeftOffset = readUEG();
14644
- frameCropRightOffset = readUEG();
14645
- frameCropTopOffset = readUEG();
14646
- frameCropBottomOffset = readUEG();
14523
+ if (lastUnitStart >= 0 && state >= 0) {
14524
+ const unit = {
14525
+ data: array.subarray(lastUnitStart, len),
14526
+ type: lastUnitType,
14527
+ state: state
14528
+ };
14529
+ units.push(unit);
14530
+ // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
14647
14531
  }
14648
- let pixelRatio = [1, 1];
14649
- if (readBoolean()) {
14650
- // vui_parameters_present_flag
14651
- if (readBoolean()) {
14652
- // aspect_ratio_info_present_flag
14653
- const aspectRatioIdc = readUByte();
14654
- switch (aspectRatioIdc) {
14655
- case 1:
14656
- pixelRatio = [1, 1];
14657
- break;
14658
- case 2:
14659
- pixelRatio = [12, 11];
14660
- break;
14661
- case 3:
14662
- pixelRatio = [10, 11];
14663
- break;
14664
- case 4:
14665
- pixelRatio = [16, 11];
14666
- break;
14667
- case 5:
14668
- pixelRatio = [40, 33];
14669
- break;
14670
- case 6:
14671
- pixelRatio = [24, 11];
14672
- break;
14673
- case 7:
14674
- pixelRatio = [20, 11];
14675
- break;
14676
- case 8:
14677
- pixelRatio = [32, 11];
14678
- break;
14679
- case 9:
14680
- pixelRatio = [80, 33];
14681
- break;
14682
- case 10:
14683
- pixelRatio = [18, 11];
14684
- break;
14685
- case 11:
14686
- pixelRatio = [15, 11];
14687
- break;
14688
- case 12:
14689
- pixelRatio = [64, 33];
14690
- break;
14691
- case 13:
14692
- pixelRatio = [160, 99];
14693
- break;
14694
- case 14:
14695
- pixelRatio = [4, 3];
14696
- break;
14697
- case 15:
14698
- pixelRatio = [3, 2];
14699
- break;
14700
- case 16:
14701
- pixelRatio = [2, 1];
14702
- break;
14703
- case 255:
14704
- {
14705
- pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
14706
- break;
14707
- }
14708
- }
14532
+ // no NALu found
14533
+ if (units.length === 0) {
14534
+ // append pes.data to previous NAL unit
14535
+ const lastUnit = this.getLastNalUnit(track.samples);
14536
+ if (lastUnit) {
14537
+ lastUnit.data = appendUint8Array(lastUnit.data, array);
14709
14538
  }
14710
14539
  }
14711
- return {
14712
- width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
14713
- height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
14714
- pixelRatio: pixelRatio
14715
- };
14540
+ track.naluState = state;
14541
+ return units;
14716
14542
  }
14717
14543
  }
14718
14544
 
@@ -14730,7 +14556,7 @@ class SampleAesDecrypter {
14730
14556
  });
14731
14557
  }
14732
14558
  decryptBuffer(encryptedData) {
14733
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
14559
+ return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
14734
14560
  }
14735
14561
 
14736
14562
  // AAC - encrypt all full 16 bytes blocks starting from offset 16
@@ -14844,7 +14670,7 @@ class TSDemuxer {
14844
14670
  this.observer = observer;
14845
14671
  this.config = config;
14846
14672
  this.typeSupported = typeSupported;
14847
- this.videoParser = null;
14673
+ this.videoParser = new AvcVideoParser();
14848
14674
  }
14849
14675
  static probe(data) {
14850
14676
  const syncOffset = TSDemuxer.syncOffset(data);
@@ -15009,16 +14835,7 @@ class TSDemuxer {
15009
14835
  case videoPid:
15010
14836
  if (stt) {
15011
14837
  if (videoData && (pes = parsePES(videoData))) {
15012
- if (this.videoParser === null) {
15013
- switch (videoTrack.segmentCodec) {
15014
- case 'avc':
15015
- this.videoParser = new AvcVideoParser();
15016
- break;
15017
- }
15018
- }
15019
- if (this.videoParser !== null) {
15020
- this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
15021
- }
14838
+ this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
15022
14839
  }
15023
14840
  videoData = {
15024
14841
  data: [],
@@ -15180,17 +14997,8 @@ class TSDemuxer {
15180
14997
  // try to parse last PES packets
15181
14998
  let pes;
15182
14999
  if (videoData && (pes = parsePES(videoData))) {
15183
- if (this.videoParser === null) {
15184
- switch (videoTrack.segmentCodec) {
15185
- case 'avc':
15186
- this.videoParser = new AvcVideoParser();
15187
- break;
15188
- }
15189
- }
15190
- if (this.videoParser !== null) {
15191
- this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
15192
- videoTrack.pesData = null;
15193
- }
15000
+ this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
15001
+ videoTrack.pesData = null;
15194
15002
  } else {
15195
15003
  // either avcData null or PES truncated, keep it for next frag parsing
15196
15004
  videoTrack.pesData = videoData;
@@ -15493,10 +15301,7 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
15493
15301
  logger.warn('Unsupported EC-3 in M2TS found');
15494
15302
  break;
15495
15303
  case 0x24:
15496
- // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
15497
- {
15498
- logger.warn('Unsupported HEVC in M2TS found');
15499
- }
15304
+ logger.warn('Unsupported HEVC in M2TS found');
15500
15305
  break;
15501
15306
  }
15502
15307
  // move to the next table entry
@@ -15719,8 +15524,6 @@ class MP4 {
15719
15524
  avc1: [],
15720
15525
  // codingname
15721
15526
  avcC: [],
15722
- hvc1: [],
15723
- hvcC: [],
15724
15527
  btrt: [],
15725
15528
  dinf: [],
15726
15529
  dref: [],
@@ -16145,10 +15948,8 @@ class MP4 {
16145
15948
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
16146
15949
  }
16147
15950
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
16148
- } else if (track.segmentCodec === 'avc') {
16149
- return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
16150
15951
  } else {
16151
- return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
15952
+ return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
16152
15953
  }
16153
15954
  }
16154
15955
  static tkhd(track) {
@@ -16286,84 +16087,6 @@ class MP4 {
16286
16087
  const result = appendUint8Array(MP4.FTYP, movie);
16287
16088
  return result;
16288
16089
  }
16289
- static hvc1(track) {
16290
- const ps = track.params;
16291
- const units = [track.vps, track.sps, track.pps];
16292
- const NALuLengthSize = 4;
16293
- 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]);
16294
-
16295
- // compute hvcC size in bytes
16296
- let length = config.length;
16297
- for (let i = 0; i < units.length; i += 1) {
16298
- length += 3;
16299
- for (let j = 0; j < units[i].length; j += 1) {
16300
- length += 2 + units[i][j].length;
16301
- }
16302
- }
16303
- const hvcC = new Uint8Array(length);
16304
- hvcC.set(config, 0);
16305
- length = config.length;
16306
- // append parameter set units: one vps, one or more sps and pps
16307
- const iMax = units.length - 1;
16308
- for (let i = 0; i < units.length; i += 1) {
16309
- hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
16310
- length += 3;
16311
- for (let j = 0; j < units[i].length; j += 1) {
16312
- hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
16313
- length += 2;
16314
- hvcC.set(units[i][j], length);
16315
- length += units[i][j].length;
16316
- }
16317
- }
16318
- const hvcc = MP4.box(MP4.types.hvcC, hvcC);
16319
- const width = track.width;
16320
- const height = track.height;
16321
- const hSpacing = track.pixelRatio[0];
16322
- const vSpacing = track.pixelRatio[1];
16323
- return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
16324
- // reserved
16325
- 0x00, 0x00, 0x00,
16326
- // reserved
16327
- 0x00, 0x01,
16328
- // data_reference_index
16329
- 0x00, 0x00,
16330
- // pre_defined
16331
- 0x00, 0x00,
16332
- // reserved
16333
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
16334
- // pre_defined
16335
- width >> 8 & 0xff, width & 0xff,
16336
- // width
16337
- height >> 8 & 0xff, height & 0xff,
16338
- // height
16339
- 0x00, 0x48, 0x00, 0x00,
16340
- // horizresolution
16341
- 0x00, 0x48, 0x00, 0x00,
16342
- // vertresolution
16343
- 0x00, 0x00, 0x00, 0x00,
16344
- // reserved
16345
- 0x00, 0x01,
16346
- // frame_count
16347
- 0x12, 0x64, 0x61, 0x69, 0x6c,
16348
- // dailymotion/hls.js
16349
- 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,
16350
- // compressorname
16351
- 0x00, 0x18,
16352
- // depth = 24
16353
- 0x11, 0x11]),
16354
- // pre_defined = -1
16355
- hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
16356
- // bufferSizeDB
16357
- 0x00, 0x2d, 0xc6, 0xc0,
16358
- // maxBitrate
16359
- 0x00, 0x2d, 0xc6, 0xc0])),
16360
- // avgBitrate
16361
- MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
16362
- // hSpacing
16363
- hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
16364
- // vSpacing
16365
- vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
16366
- }
16367
16090
  }
16368
16091
  MP4.types = void 0;
16369
16092
  MP4.HDLR_TYPES = void 0;
@@ -16739,9 +16462,9 @@ class MP4Remuxer {
16739
16462
  const foundOverlap = delta < -1;
16740
16463
  if (foundHole || foundOverlap) {
16741
16464
  if (foundHole) {
16742
- logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
16465
+ logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
16743
16466
  } else {
16744
- logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
16467
+ logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
16745
16468
  }
16746
16469
  if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
16747
16470
  firstDTS = nextAvcDts;
@@ -16750,24 +16473,12 @@ class MP4Remuxer {
16750
16473
  inputSamples[0].dts = firstDTS;
16751
16474
  inputSamples[0].pts = firstPTS;
16752
16475
  } else {
16753
- let isPTSOrderRetained = true;
16754
16476
  for (let i = 0; i < inputSamples.length; i++) {
16755
- if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
16477
+ if (inputSamples[i].dts > firstPTS) {
16756
16478
  break;
16757
16479
  }
16758
- const prevPTS = inputSamples[i].pts;
16759
16480
  inputSamples[i].dts -= delta;
16760
16481
  inputSamples[i].pts -= delta;
16761
-
16762
- // check to see if this sample's PTS order has changed
16763
- // relative to the next one
16764
- if (i < inputSamples.length - 1) {
16765
- const nextSamplePTS = inputSamples[i + 1].pts;
16766
- const currentSamplePTS = inputSamples[i].pts;
16767
- const currentOrder = nextSamplePTS <= currentSamplePTS;
16768
- const prevOrder = nextSamplePTS <= prevPTS;
16769
- isPTSOrderRetained = currentOrder == prevOrder;
16770
- }
16771
16482
  }
16772
16483
  }
16773
16484
  logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
@@ -16915,7 +16626,7 @@ class MP4Remuxer {
16915
16626
  }
16916
16627
  }
16917
16628
  }
16918
- // next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
16629
+ // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
16919
16630
  mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
16920
16631
  this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
16921
16632
  this.videoSampleDuration = mp4SampleDuration;
@@ -17048,7 +16759,7 @@ class MP4Remuxer {
17048
16759
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
17049
16760
  for (let j = 0; j < missing; j++) {
17050
16761
  const newStamp = Math.max(nextPts, 0);
17051
- let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
16762
+ let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
17052
16763
  if (!fillFrame) {
17053
16764
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
17054
16765
  fillFrame = sample.unit.subarray();
@@ -17176,7 +16887,7 @@ class MP4Remuxer {
17176
16887
  // samples count of this segment's duration
17177
16888
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
17178
16889
  // silent frame
17179
- const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
16890
+ const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
17180
16891
  logger.warn('[mp4-remuxer]: remux empty Audio');
17181
16892
  // Can't remux if we can't generate a silent frame...
17182
16893
  if (!silentFrame) {
@@ -17567,15 +17278,13 @@ class Transmuxer {
17567
17278
  initSegmentData
17568
17279
  } = transmuxConfig;
17569
17280
  const keyData = getEncryptionType(uintData, decryptdata);
17570
- if (keyData && isFullSegmentEncryption(keyData.method)) {
17281
+ if (keyData && keyData.method === 'AES-128') {
17571
17282
  const decrypter = this.getDecrypter();
17572
- const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
17573
-
17574
17283
  // Software decryption is synchronous; webCrypto is not
17575
17284
  if (decrypter.isSync()) {
17576
17285
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
17577
17286
  // data is handled in the flush() call
17578
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
17287
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
17579
17288
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
17580
17289
  const loadingParts = chunkMeta.part > -1;
17581
17290
  if (loadingParts) {
@@ -17587,7 +17296,7 @@ class Transmuxer {
17587
17296
  }
17588
17297
  uintData = new Uint8Array(decryptedData);
17589
17298
  } else {
17590
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
17299
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
17591
17300
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
17592
17301
  // the decrypted data has been transmuxed
17593
17302
  const result = this.push(decryptedData, null, chunkMeta);
@@ -18241,7 +17950,14 @@ class TransmuxerInterface {
18241
17950
  this.observer = new EventEmitter();
18242
17951
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
18243
17952
  this.observer.on(Events.ERROR, forwardMessage);
18244
- const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
17953
+ const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
17954
+ isTypeSupported: () => false
17955
+ };
17956
+ const m2tsTypeSupported = {
17957
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
17958
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
17959
+ ac3: false
17960
+ };
18245
17961
 
18246
17962
  // navigator.vendor is not always available in Web Worker
18247
17963
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -18505,9 +18221,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
18505
18221
  const MAX_START_GAP_JUMP = 2.0;
18506
18222
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
18507
18223
  const SKIP_BUFFER_RANGE_START = 0.05;
18508
- class GapController extends Logger {
18224
+ class GapController {
18509
18225
  constructor(config, media, fragmentTracker, hls) {
18510
- super('gap-controller', hls.logger);
18511
18226
  this.config = void 0;
18512
18227
  this.media = null;
18513
18228
  this.fragmentTracker = void 0;
@@ -18517,7 +18232,6 @@ class GapController extends Logger {
18517
18232
  this.stalled = null;
18518
18233
  this.moved = false;
18519
18234
  this.seeking = false;
18520
- this.ended = 0;
18521
18235
  this.config = config;
18522
18236
  this.media = media;
18523
18237
  this.fragmentTracker = fragmentTracker;
@@ -18535,7 +18249,7 @@ class GapController extends Logger {
18535
18249
  *
18536
18250
  * @param lastCurrentTime - Previously read playhead position
18537
18251
  */
18538
- poll(lastCurrentTime, activeFrag, levelDetails, state) {
18252
+ poll(lastCurrentTime, activeFrag) {
18539
18253
  const {
18540
18254
  config,
18541
18255
  media,
@@ -18554,7 +18268,6 @@ class GapController extends Logger {
18554
18268
 
18555
18269
  // The playhead is moving, no-op
18556
18270
  if (currentTime !== lastCurrentTime) {
18557
- this.ended = 0;
18558
18271
  this.moved = true;
18559
18272
  if (!seeking) {
18560
18273
  this.nudgeRetry = 0;
@@ -18563,7 +18276,7 @@ class GapController extends Logger {
18563
18276
  // The playhead is now moving, but was previously stalled
18564
18277
  if (this.stallReported) {
18565
18278
  const _stalledDuration = self.performance.now() - stalled;
18566
- this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
18279
+ logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
18567
18280
  this.stallReported = false;
18568
18281
  }
18569
18282
  this.stalled = null;
@@ -18599,6 +18312,7 @@ class GapController extends Logger {
18599
18312
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
18600
18313
  // The addition poll gives the browser a chance to jump the gap for us
18601
18314
  if (!this.moved && this.stalled !== null) {
18315
+ var _level$details;
18602
18316
  // There is no playable buffer (seeked, waiting for buffer)
18603
18317
  const isBuffered = bufferInfo.len > 0;
18604
18318
  if (!isBuffered && !nextStart) {
@@ -18610,8 +18324,9 @@ class GapController extends Logger {
18610
18324
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
18611
18325
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
18612
18326
  // that begins over 1 target duration after the video start position.
18613
- const isLive = !!(levelDetails != null && levelDetails.live);
18614
- const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
18327
+ const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
18328
+ const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
18329
+ const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
18615
18330
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
18616
18331
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
18617
18332
  if (!media.paused) {
@@ -18629,17 +18344,6 @@ class GapController extends Logger {
18629
18344
  }
18630
18345
  const stalledDuration = tnow - stalled;
18631
18346
  if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
18632
- // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
18633
- if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
18634
- if (stalledDuration < 1000 || this.ended) {
18635
- return;
18636
- }
18637
- this.ended = currentTime;
18638
- this.hls.trigger(Events.MEDIA_ENDED, {
18639
- stalled: true
18640
- });
18641
- return;
18642
- }
18643
18347
  // Report stalling after trying to fix
18644
18348
  this._reportStall(bufferInfo);
18645
18349
  if (!this.media) {
@@ -18683,7 +18387,7 @@ class GapController extends Logger {
18683
18387
  // needs to cross some sort of threshold covering all source-buffers content
18684
18388
  // to start playing properly.
18685
18389
  if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
18686
- this.warn('Trying to nudge playhead over buffer-hole');
18390
+ logger.warn('Trying to nudge playhead over buffer-hole');
18687
18391
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
18688
18392
  // We only try to jump the hole if it's under the configured size
18689
18393
  // Reset stalled so to rearm watchdog timer
@@ -18707,7 +18411,7 @@ class GapController extends Logger {
18707
18411
  // Report stalled error once
18708
18412
  this.stallReported = true;
18709
18413
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
18710
- this.warn(error.message);
18414
+ logger.warn(error.message);
18711
18415
  hls.trigger(Events.ERROR, {
18712
18416
  type: ErrorTypes.MEDIA_ERROR,
18713
18417
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -18775,7 +18479,7 @@ class GapController extends Logger {
18775
18479
  }
18776
18480
  }
18777
18481
  const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
18778
- this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
18482
+ logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
18779
18483
  this.moved = true;
18780
18484
  this.stalled = null;
18781
18485
  media.currentTime = targetTime;
@@ -18816,7 +18520,7 @@ class GapController extends Logger {
18816
18520
  const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
18817
18521
  // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
18818
18522
  const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
18819
- this.warn(error.message);
18523
+ logger.warn(error.message);
18820
18524
  media.currentTime = targetTime;
18821
18525
  hls.trigger(Events.ERROR, {
18822
18526
  type: ErrorTypes.MEDIA_ERROR,
@@ -18826,7 +18530,7 @@ class GapController extends Logger {
18826
18530
  });
18827
18531
  } else {
18828
18532
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
18829
- this.error(error.message);
18533
+ logger.error(error.message);
18830
18534
  hls.trigger(Events.ERROR, {
18831
18535
  type: ErrorTypes.MEDIA_ERROR,
18832
18536
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -18841,7 +18545,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
18841
18545
 
18842
18546
  class StreamController extends BaseStreamController {
18843
18547
  constructor(hls, fragmentTracker, keyLoader) {
18844
- super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
18548
+ super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
18845
18549
  this.audioCodecSwap = false;
18846
18550
  this.gapController = null;
18847
18551
  this.level = -1;
@@ -18849,43 +18553,27 @@ class StreamController extends BaseStreamController {
18849
18553
  this.altAudio = false;
18850
18554
  this.audioOnly = false;
18851
18555
  this.fragPlaying = null;
18556
+ this.onvplaying = null;
18557
+ this.onvseeked = null;
18852
18558
  this.fragLastKbps = 0;
18853
18559
  this.couldBacktrack = false;
18854
18560
  this.backtrackFragment = null;
18855
18561
  this.audioCodecSwitch = false;
18856
18562
  this.videoBuffer = null;
18857
- this.onMediaPlaying = () => {
18858
- // tick to speed up FRAG_CHANGED triggering
18859
- this.tick();
18860
- };
18861
- this.onMediaSeeked = () => {
18862
- const media = this.media;
18863
- const currentTime = media ? media.currentTime : null;
18864
- if (isFiniteNumber(currentTime)) {
18865
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
18866
- }
18867
-
18868
- // If seeked was issued before buffer was appended do not tick immediately
18869
- const bufferInfo = this.getMainFwdBufferInfo();
18870
- if (bufferInfo === null || bufferInfo.len === 0) {
18871
- this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
18872
- return;
18873
- }
18874
-
18875
- // tick to speed up FRAG_CHANGED triggering
18876
- this.tick();
18877
- };
18878
- this.registerListeners();
18563
+ this._registerListeners();
18879
18564
  }
18880
- registerListeners() {
18881
- super.registerListeners();
18565
+ _registerListeners() {
18882
18566
  const {
18883
18567
  hls
18884
18568
  } = this;
18569
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
18570
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
18571
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18885
18572
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18886
18573
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
18887
18574
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
18888
18575
  hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
18576
+ hls.on(Events.ERROR, this.onError, this);
18889
18577
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
18890
18578
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
18891
18579
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -18893,14 +18581,17 @@ class StreamController extends BaseStreamController {
18893
18581
  hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
18894
18582
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
18895
18583
  }
18896
- unregisterListeners() {
18897
- super.unregisterListeners();
18584
+ _unregisterListeners() {
18898
18585
  const {
18899
18586
  hls
18900
18587
  } = this;
18588
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
18589
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
18590
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18901
18591
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18902
18592
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
18903
18593
  hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
18594
+ hls.off(Events.ERROR, this.onError, this);
18904
18595
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
18905
18596
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
18906
18597
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -18909,9 +18600,7 @@ class StreamController extends BaseStreamController {
18909
18600
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
18910
18601
  }
18911
18602
  onHandlerDestroying() {
18912
- // @ts-ignore
18913
- this.onMediaPlaying = this.onMediaSeeked = null;
18914
- this.unregisterListeners();
18603
+ this._unregisterListeners();
18915
18604
  super.onHandlerDestroying();
18916
18605
  }
18917
18606
  startLoad(startPosition) {
@@ -19031,7 +18720,7 @@ class StreamController extends BaseStreamController {
19031
18720
  if (this.altAudio && this.audioOnly) {
19032
18721
  return;
19033
18722
  }
19034
- if (!this.buffering || !(levels != null && levels[level])) {
18723
+ if (!(levels != null && levels[level])) {
19035
18724
  return;
19036
18725
  }
19037
18726
  const levelInfo = levels[level];
@@ -19239,17 +18928,20 @@ class StreamController extends BaseStreamController {
19239
18928
  onMediaAttached(event, data) {
19240
18929
  super.onMediaAttached(event, data);
19241
18930
  const media = data.media;
19242
- media.addEventListener('playing', this.onMediaPlaying);
19243
- media.addEventListener('seeked', this.onMediaSeeked);
18931
+ this.onvplaying = this.onMediaPlaying.bind(this);
18932
+ this.onvseeked = this.onMediaSeeked.bind(this);
18933
+ media.addEventListener('playing', this.onvplaying);
18934
+ media.addEventListener('seeked', this.onvseeked);
19244
18935
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
19245
18936
  }
19246
18937
  onMediaDetaching() {
19247
18938
  const {
19248
18939
  media
19249
18940
  } = this;
19250
- if (media) {
19251
- media.removeEventListener('playing', this.onMediaPlaying);
19252
- media.removeEventListener('seeked', this.onMediaSeeked);
18941
+ if (media && this.onvplaying && this.onvseeked) {
18942
+ media.removeEventListener('playing', this.onvplaying);
18943
+ media.removeEventListener('seeked', this.onvseeked);
18944
+ this.onvplaying = this.onvseeked = null;
19253
18945
  this.videoBuffer = null;
19254
18946
  }
19255
18947
  this.fragPlaying = null;
@@ -19259,6 +18951,27 @@ class StreamController extends BaseStreamController {
19259
18951
  }
19260
18952
  super.onMediaDetaching();
19261
18953
  }
18954
+ onMediaPlaying() {
18955
+ // tick to speed up FRAG_CHANGED triggering
18956
+ this.tick();
18957
+ }
18958
+ onMediaSeeked() {
18959
+ const media = this.media;
18960
+ const currentTime = media ? media.currentTime : null;
18961
+ if (isFiniteNumber(currentTime)) {
18962
+ this.log(`Media seeked to ${currentTime.toFixed(3)}`);
18963
+ }
18964
+
18965
+ // If seeked was issued before buffer was appended do not tick immediately
18966
+ const bufferInfo = this.getMainFwdBufferInfo();
18967
+ if (bufferInfo === null || bufferInfo.len === 0) {
18968
+ this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
18969
+ return;
18970
+ }
18971
+
18972
+ // tick to speed up FRAG_CHANGED triggering
18973
+ this.tick();
18974
+ }
19262
18975
  onManifestLoading() {
19263
18976
  // reset buffer on manifest loading
19264
18977
  this.log('Trigger BUFFER_RESET');
@@ -19550,10 +19263,8 @@ class StreamController extends BaseStreamController {
19550
19263
  }
19551
19264
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
19552
19265
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
19553
- const state = this.state;
19554
- const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
19555
- const levelDetails = this.getLevelDetails();
19556
- gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
19266
+ const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
19267
+ gapController.poll(this.lastCurrentTime, activeFrag);
19557
19268
  }
19558
19269
  this.lastCurrentTime = media.currentTime;
19559
19270
  }
@@ -19991,7 +19702,7 @@ class Hls {
19991
19702
  * Get the video-dev/hls.js package version.
19992
19703
  */
19993
19704
  static get version() {
19994
- return "1.5.5-0.canary.9995";
19705
+ return "1.5.5";
19995
19706
  }
19996
19707
 
19997
19708
  /**
@@ -20054,12 +19765,9 @@ class Hls {
20054
19765
  * The configuration object provided on player instantiation.
20055
19766
  */
20056
19767
  this.userConfig = void 0;
20057
- /**
20058
- * The logger functions used by this player instance, configured on player instantiation.
20059
- */
20060
- this.logger = void 0;
20061
19768
  this.coreComponents = void 0;
20062
19769
  this.networkControllers = void 0;
19770
+ this.started = false;
20063
19771
  this._emitter = new EventEmitter();
20064
19772
  this._autoLevelCapping = -1;
20065
19773
  this._maxHdcpLevel = null;
@@ -20076,11 +19784,11 @@ class Hls {
20076
19784
  this._media = null;
20077
19785
  this.url = null;
20078
19786
  this.triggeringException = void 0;
20079
- const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
20080
- const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
19787
+ enableLogs(userConfig.debug || false, 'Hls instance');
19788
+ const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
20081
19789
  this.userConfig = userConfig;
20082
19790
  if (config.progressive) {
20083
- enableStreamingMode(config, logger);
19791
+ enableStreamingMode(config);
20084
19792
  }
20085
19793
 
20086
19794
  // core controllers and network loaders
@@ -20179,7 +19887,7 @@ class Hls {
20179
19887
  try {
20180
19888
  return this.emit(event, event, eventObject);
20181
19889
  } catch (error) {
20182
- this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
19890
+ logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
20183
19891
  // Prevent recursion in error event handlers that throw #5497
20184
19892
  if (!this.triggeringException) {
20185
19893
  this.triggeringException = true;
@@ -20205,7 +19913,7 @@ class Hls {
20205
19913
  * Dispose of the instance
20206
19914
  */
20207
19915
  destroy() {
20208
- this.logger.log('destroy');
19916
+ logger.log('destroy');
20209
19917
  this.trigger(Events.DESTROYING, undefined);
20210
19918
  this.detachMedia();
20211
19919
  this.removeAllListeners();
@@ -20226,7 +19934,7 @@ class Hls {
20226
19934
  * Attaches Hls.js to a media element
20227
19935
  */
20228
19936
  attachMedia(media) {
20229
- this.logger.log('attachMedia');
19937
+ logger.log('attachMedia');
20230
19938
  this._media = media;
20231
19939
  this.trigger(Events.MEDIA_ATTACHING, {
20232
19940
  media: media
@@ -20237,7 +19945,7 @@ class Hls {
20237
19945
  * Detach Hls.js from the media
20238
19946
  */
20239
19947
  detachMedia() {
20240
- this.logger.log('detachMedia');
19948
+ logger.log('detachMedia');
20241
19949
  this.trigger(Events.MEDIA_DETACHING, undefined);
20242
19950
  this._media = null;
20243
19951
  }
@@ -20254,7 +19962,7 @@ class Hls {
20254
19962
  });
20255
19963
  this._autoLevelCapping = -1;
20256
19964
  this._maxHdcpLevel = null;
20257
- this.logger.log(`loadSource:${loadingSource}`);
19965
+ logger.log(`loadSource:${loadingSource}`);
20258
19966
  if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
20259
19967
  this.detachMedia();
20260
19968
  this.attachMedia(media);
@@ -20273,7 +19981,8 @@ class Hls {
20273
19981
  * Defaults to -1 (None: starts from earliest point)
20274
19982
  */
20275
19983
  startLoad(startPosition = -1) {
20276
- this.logger.log(`startLoad(${startPosition})`);
19984
+ logger.log(`startLoad(${startPosition})`);
19985
+ this.started = true;
20277
19986
  this.networkControllers.forEach(controller => {
20278
19987
  controller.startLoad(startPosition);
20279
19988
  });
@@ -20283,31 +19992,34 @@ class Hls {
20283
19992
  * Stop loading of any stream data.
20284
19993
  */
20285
19994
  stopLoad() {
20286
- this.logger.log('stopLoad');
19995
+ logger.log('stopLoad');
19996
+ this.started = false;
20287
19997
  this.networkControllers.forEach(controller => {
20288
19998
  controller.stopLoad();
20289
19999
  });
20290
20000
  }
20291
20001
 
20292
20002
  /**
20293
- * Resumes stream controller segment loading after `pauseBuffering` has been called.
20003
+ * Resumes stream controller segment loading if previously started.
20294
20004
  */
20295
20005
  resumeBuffering() {
20296
- this.networkControllers.forEach(controller => {
20297
- if (controller.resumeBuffering) {
20298
- controller.resumeBuffering();
20299
- }
20300
- });
20006
+ if (this.started) {
20007
+ this.networkControllers.forEach(controller => {
20008
+ if ('fragmentLoader' in controller) {
20009
+ controller.startLoad(-1);
20010
+ }
20011
+ });
20012
+ }
20301
20013
  }
20302
20014
 
20303
20015
  /**
20304
- * Prevents stream controller from loading new segments until `resumeBuffering` is called.
20016
+ * Stops stream controller segment loading without changing 'started' state like stopLoad().
20305
20017
  * This allows for media buffering to be paused without interupting playlist loading.
20306
20018
  */
20307
20019
  pauseBuffering() {
20308
20020
  this.networkControllers.forEach(controller => {
20309
- if (controller.pauseBuffering) {
20310
- controller.pauseBuffering();
20021
+ if ('fragmentLoader' in controller) {
20022
+ controller.stopLoad();
20311
20023
  }
20312
20024
  });
20313
20025
  }
@@ -20316,7 +20028,7 @@ class Hls {
20316
20028
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
20317
20029
  */
20318
20030
  swapAudioCodec() {
20319
- this.logger.log('swapAudioCodec');
20031
+ logger.log('swapAudioCodec');
20320
20032
  this.streamController.swapAudioCodec();
20321
20033
  }
20322
20034
 
@@ -20327,7 +20039,7 @@ class Hls {
20327
20039
  * Automatic recovery of media-errors by this process is configurable.
20328
20040
  */
20329
20041
  recoverMediaError() {
20330
- this.logger.log('recoverMediaError');
20042
+ logger.log('recoverMediaError');
20331
20043
  const media = this._media;
20332
20044
  this.detachMedia();
20333
20045
  if (media) {
@@ -20357,7 +20069,7 @@ class Hls {
20357
20069
  * 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.
20358
20070
  */
20359
20071
  set currentLevel(newLevel) {
20360
- this.logger.log(`set currentLevel:${newLevel}`);
20072
+ logger.log(`set currentLevel:${newLevel}`);
20361
20073
  this.levelController.manualLevel = newLevel;
20362
20074
  this.streamController.immediateLevelSwitch();
20363
20075
  }
@@ -20376,7 +20088,7 @@ class Hls {
20376
20088
  * @param newLevel - Pass -1 for automatic level selection
20377
20089
  */
20378
20090
  set nextLevel(newLevel) {
20379
- this.logger.log(`set nextLevel:${newLevel}`);
20091
+ logger.log(`set nextLevel:${newLevel}`);
20380
20092
  this.levelController.manualLevel = newLevel;
20381
20093
  this.streamController.nextLevelSwitch();
20382
20094
  }
@@ -20395,7 +20107,7 @@ class Hls {
20395
20107
  * @param newLevel - Pass -1 for automatic level selection
20396
20108
  */
20397
20109
  set loadLevel(newLevel) {
20398
- this.logger.log(`set loadLevel:${newLevel}`);
20110
+ logger.log(`set loadLevel:${newLevel}`);
20399
20111
  this.levelController.manualLevel = newLevel;
20400
20112
  }
20401
20113
 
@@ -20426,7 +20138,7 @@ class Hls {
20426
20138
  * Sets "first-level", see getter.
20427
20139
  */
20428
20140
  set firstLevel(newLevel) {
20429
- this.logger.log(`set firstLevel:${newLevel}`);
20141
+ logger.log(`set firstLevel:${newLevel}`);
20430
20142
  this.levelController.firstLevel = newLevel;
20431
20143
  }
20432
20144
 
@@ -20451,7 +20163,7 @@ class Hls {
20451
20163
  * (determined from download of first segment)
20452
20164
  */
20453
20165
  set startLevel(newLevel) {
20454
- this.logger.log(`set startLevel:${newLevel}`);
20166
+ logger.log(`set startLevel:${newLevel}`);
20455
20167
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
20456
20168
  if (newLevel !== -1) {
20457
20169
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -20526,7 +20238,7 @@ class Hls {
20526
20238
  */
20527
20239
  set autoLevelCapping(newLevel) {
20528
20240
  if (this._autoLevelCapping !== newLevel) {
20529
- this.logger.log(`set autoLevelCapping:${newLevel}`);
20241
+ logger.log(`set autoLevelCapping:${newLevel}`);
20530
20242
  this._autoLevelCapping = newLevel;
20531
20243
  this.levelController.checkMaxAutoUpdated();
20532
20244
  }
@@ -20805,5 +20517,5 @@ var KeySystemFormats = empty.KeySystemFormats;
20805
20517
  var KeySystems = empty.KeySystems;
20806
20518
  var SubtitleStreamController = empty.SubtitleStreamController;
20807
20519
  var TimelineController = empty.TimelineController;
20808
- 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 };
20520
+ export { AbrController, AttrList, Cues as AudioStreamController, Cues as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, Cues as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, Cues as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, Cues as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported };
20809
20521
  //# sourceMappingURL=hls.light.mjs.map