hls.js 1.5.7 → 1.5.8-0.canary.10044

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 (72) hide show
  1. package/README.md +2 -1
  2. package/dist/hls-demo.js +10 -0
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +2314 -1298
  5. package/dist/hls.js.d.ts +97 -84
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1486 -1075
  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 +1195 -789
  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 +1979 -982
  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 +22 -22
  20. package/src/config.ts +3 -2
  21. package/src/controller/abr-controller.ts +24 -20
  22. package/src/controller/audio-stream-controller.ts +68 -74
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +20 -8
  25. package/src/controller/base-stream-controller.ts +157 -36
  26. package/src/controller/buffer-controller.ts +203 -67
  27. package/src/controller/buffer-operation-queue.ts +16 -19
  28. package/src/controller/cap-level-controller.ts +2 -2
  29. package/src/controller/cmcd-controller.ts +27 -6
  30. package/src/controller/content-steering-controller.ts +8 -6
  31. package/src/controller/eme-controller.ts +9 -22
  32. package/src/controller/error-controller.ts +6 -8
  33. package/src/controller/fps-controller.ts +2 -3
  34. package/src/controller/fragment-tracker.ts +15 -11
  35. package/src/controller/gap-controller.ts +43 -16
  36. package/src/controller/latency-controller.ts +9 -11
  37. package/src/controller/level-controller.ts +12 -18
  38. package/src/controller/stream-controller.ts +36 -31
  39. package/src/controller/subtitle-stream-controller.ts +28 -40
  40. package/src/controller/subtitle-track-controller.ts +5 -3
  41. package/src/controller/timeline-controller.ts +23 -30
  42. package/src/crypt/aes-crypto.ts +21 -2
  43. package/src/crypt/decrypter-aes-mode.ts +4 -0
  44. package/src/crypt/decrypter.ts +32 -18
  45. package/src/crypt/fast-aes-key.ts +24 -5
  46. package/src/demux/audio/adts.ts +9 -4
  47. package/src/demux/sample-aes.ts +2 -0
  48. package/src/demux/transmuxer-interface.ts +4 -12
  49. package/src/demux/transmuxer-worker.ts +4 -4
  50. package/src/demux/transmuxer.ts +16 -3
  51. package/src/demux/tsdemuxer.ts +71 -37
  52. package/src/demux/video/avc-video-parser.ts +208 -119
  53. package/src/demux/video/base-video-parser.ts +134 -2
  54. package/src/demux/video/exp-golomb.ts +0 -208
  55. package/src/demux/video/hevc-video-parser.ts +746 -0
  56. package/src/events.ts +7 -0
  57. package/src/hls.ts +49 -37
  58. package/src/loader/fragment-loader.ts +9 -2
  59. package/src/loader/key-loader.ts +2 -0
  60. package/src/loader/level-key.ts +10 -9
  61. package/src/loader/playlist-loader.ts +4 -5
  62. package/src/remux/mp4-generator.ts +196 -1
  63. package/src/remux/mp4-remuxer.ts +23 -7
  64. package/src/task-loop.ts +5 -2
  65. package/src/types/component-api.ts +2 -0
  66. package/src/types/demuxer.ts +3 -0
  67. package/src/types/events.ts +4 -0
  68. package/src/utils/buffer-helper.ts +12 -31
  69. package/src/utils/codecs.ts +34 -5
  70. package/src/utils/encryption-methods-util.ts +21 -0
  71. package/src/utils/logger.ts +54 -24
  72. package/src/utils/mp4-tools.ts +4 -2
package/dist/hls.mjs CHANGED
@@ -256,6 +256,7 @@ let Events = /*#__PURE__*/function (Events) {
256
256
  Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
257
257
  Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
258
258
  Events["MEDIA_DETACHED"] = "hlsMediaDetached";
259
+ Events["MEDIA_ENDED"] = "hlsMediaEnded";
259
260
  Events["BUFFER_RESET"] = "hlsBufferReset";
260
261
  Events["BUFFER_CODECS"] = "hlsBufferCodecs";
261
262
  Events["BUFFER_CREATED"] = "hlsBufferCreated";
@@ -369,58 +370,6 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
369
370
  return ErrorDetails;
370
371
  }({});
371
372
 
372
- const noop = function noop() {};
373
- const fakeLogger = {
374
- trace: noop,
375
- debug: noop,
376
- log: noop,
377
- warn: noop,
378
- info: noop,
379
- error: noop
380
- };
381
- let exportedLogger = fakeLogger;
382
-
383
- // let lastCallTime;
384
- // function formatMsgWithTimeInfo(type, msg) {
385
- // const now = Date.now();
386
- // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
387
- // lastCallTime = now;
388
- // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
389
- // return msg;
390
- // }
391
-
392
- function consolePrintFn(type) {
393
- const func = self.console[type];
394
- if (func) {
395
- return func.bind(self.console, `[${type}] >`);
396
- }
397
- return noop;
398
- }
399
- function exportLoggerFunctions(debugConfig, ...functions) {
400
- functions.forEach(function (type) {
401
- exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
402
- });
403
- }
404
- function enableLogs(debugConfig, id) {
405
- // check that console is available
406
- if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
407
- exportLoggerFunctions(debugConfig,
408
- // Remove out from list here to hard-disable a log-level
409
- // 'trace',
410
- 'debug', 'log', 'info', 'warn', 'error');
411
- // Some browsers don't allow to use bind on console object anyway
412
- // fallback to default if needed
413
- try {
414
- exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.7"}`);
415
- } catch (e) {
416
- exportedLogger = fakeLogger;
417
- }
418
- } else {
419
- exportedLogger = fakeLogger;
420
- }
421
- }
422
- const logger = exportedLogger;
423
-
424
373
  const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
425
374
  const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
426
375
 
@@ -502,6 +451,79 @@ class AttrList {
502
451
  }
503
452
  }
504
453
 
454
+ class Logger {
455
+ constructor(label, logger) {
456
+ this.trace = void 0;
457
+ this.debug = void 0;
458
+ this.log = void 0;
459
+ this.warn = void 0;
460
+ this.info = void 0;
461
+ this.error = void 0;
462
+ const lb = `[${label}]:`;
463
+ this.trace = noop;
464
+ this.debug = logger.debug.bind(null, lb);
465
+ this.log = logger.log.bind(null, lb);
466
+ this.warn = logger.warn.bind(null, lb);
467
+ this.info = logger.info.bind(null, lb);
468
+ this.error = logger.error.bind(null, lb);
469
+ }
470
+ }
471
+ const noop = function noop() {};
472
+ const fakeLogger = {
473
+ trace: noop,
474
+ debug: noop,
475
+ log: noop,
476
+ warn: noop,
477
+ info: noop,
478
+ error: noop
479
+ };
480
+ function createLogger() {
481
+ return _extends({}, fakeLogger);
482
+ }
483
+
484
+ // let lastCallTime;
485
+ // function formatMsgWithTimeInfo(type, msg) {
486
+ // const now = Date.now();
487
+ // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
488
+ // lastCallTime = now;
489
+ // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
490
+ // return msg;
491
+ // }
492
+
493
+ function consolePrintFn(type, id) {
494
+ const func = self.console[type];
495
+ return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
496
+ }
497
+ function getLoggerFn(key, debugConfig, id) {
498
+ return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
499
+ }
500
+ const exportedLogger = createLogger();
501
+ function enableLogs(debugConfig, context, id) {
502
+ // check that console is available
503
+ const newLogger = createLogger();
504
+ if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
505
+ const keys = [
506
+ // Remove out from list here to hard-disable a log-level
507
+ // 'trace',
508
+ 'debug', 'log', 'info', 'warn', 'error'];
509
+ keys.forEach(key => {
510
+ newLogger[key] = getLoggerFn(key, debugConfig, id);
511
+ });
512
+ // Some browsers don't allow to use bind on console object anyway
513
+ // fallback to default if needed
514
+ try {
515
+ newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.8-0.canary.10044"}`);
516
+ } catch (e) {
517
+ /* log fn threw an exception. All logger methods are no-ops. */
518
+ return createLogger();
519
+ }
520
+ }
521
+ // global exported logger uses the log methods from last call to `enableLogs`
522
+ _extends(exportedLogger, newLogger);
523
+ return newLogger;
524
+ }
525
+ const logger = exportedLogger;
526
+
505
527
  // Avoid exporting const enum so that these values can be inlined
506
528
 
507
529
  function isDateRangeCueAttribute(attrName) {
@@ -1036,6 +1058,26 @@ function strToUtf8array(str) {
1036
1058
  return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
1037
1059
  }
1038
1060
 
1061
+ var DecrypterAesMode = {
1062
+ cbc: 0,
1063
+ ctr: 1
1064
+ };
1065
+
1066
+ function isFullSegmentEncryption(method) {
1067
+ return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
1068
+ }
1069
+ function getAesModeFromFullSegmentMethod(method) {
1070
+ switch (method) {
1071
+ case 'AES-128':
1072
+ case 'AES-256':
1073
+ return DecrypterAesMode.cbc;
1074
+ case 'AES-256-CTR':
1075
+ return DecrypterAesMode.ctr;
1076
+ default:
1077
+ throw new Error(`invalid full segment method ${method}`);
1078
+ }
1079
+ }
1080
+
1039
1081
  /** returns `undefined` is `self` is missing, e.g. in node */
1040
1082
  const optionalSelf = typeof self !== 'undefined' ? self : undefined;
1041
1083
 
@@ -1784,7 +1826,7 @@ function parseStsd(stsd) {
1784
1826
  {
1785
1827
  const codecBox = findBox(sampleEntries, [fourCC])[0];
1786
1828
  const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
1787
- if (esdsBox && esdsBox.length > 12) {
1829
+ if (esdsBox && esdsBox.length > 7) {
1788
1830
  let i = 4;
1789
1831
  // ES Descriptor tag
1790
1832
  if (esdsBox[i++] !== 0x03) {
@@ -1899,7 +1941,9 @@ function parseStsd(stsd) {
1899
1941
  }
1900
1942
  function skipBERInteger(bytes, i) {
1901
1943
  const limit = i + 5;
1902
- while (bytes[i++] & 0x80 && i < limit) {}
1944
+ while (bytes[i++] & 0x80 && i < limit) {
1945
+ /* do nothing */
1946
+ }
1903
1947
  return i;
1904
1948
  }
1905
1949
  function toHex(x) {
@@ -2688,12 +2732,12 @@ class LevelKey {
2688
2732
  this.keyFormatVersions = formatversions;
2689
2733
  this.iv = iv;
2690
2734
  this.encrypted = method ? method !== 'NONE' : false;
2691
- this.isCommonEncryption = this.encrypted && method !== 'AES-128';
2735
+ this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
2692
2736
  }
2693
2737
  isSupported() {
2694
2738
  // If it's Segment encryption or No encryption, just select that key system
2695
2739
  if (this.method) {
2696
- if (this.method === 'AES-128' || this.method === 'NONE') {
2740
+ if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
2697
2741
  return true;
2698
2742
  }
2699
2743
  if (this.keyFormat === 'identity') {
@@ -2715,14 +2759,13 @@ class LevelKey {
2715
2759
  if (!this.encrypted || !this.uri) {
2716
2760
  return null;
2717
2761
  }
2718
- if (this.method === 'AES-128' && this.uri && !this.iv) {
2762
+ if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
2719
2763
  if (typeof sn !== 'number') {
2720
2764
  // We are fetching decryption data for a initialization segment
2721
- // If the segment was encrypted with AES-128
2765
+ // If the segment was encrypted with AES-128/256
2722
2766
  // It must have an IV defined. We cannot substitute the Segment Number in.
2723
- if (this.method === 'AES-128' && !this.iv) {
2724
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2725
- }
2767
+ logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2768
+
2726
2769
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
2727
2770
  sn = 0;
2728
2771
  }
@@ -3001,23 +3044,28 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
3001
3044
  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
3002
3045
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
3003
3046
  }
3004
-
3005
- // Idealy fLaC and Opus would be first (spec-compliant) but
3006
- // some browsers will report that fLaC is supported then fail.
3007
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3008
3047
  const codecsToCheck = {
3048
+ // Idealy fLaC and Opus would be first (spec-compliant) but
3049
+ // some browsers will report that fLaC is supported then fail.
3050
+ // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3009
3051
  flac: ['flac', 'fLaC', 'FLAC'],
3010
- opus: ['opus', 'Opus']
3052
+ opus: ['opus', 'Opus'],
3053
+ // Replace audio codec info if browser does not support mp4a.40.34,
3054
+ // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
3055
+ 'mp4a.40.34': ['mp3']
3011
3056
  }[lowerCaseCodec];
3012
3057
  for (let i = 0; i < codecsToCheck.length; i++) {
3058
+ var _getMediaSource;
3013
3059
  if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
3014
3060
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
3015
3061
  return codecsToCheck[i];
3062
+ } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
3063
+ return '';
3016
3064
  }
3017
3065
  }
3018
3066
  return lowerCaseCodec;
3019
3067
  }
3020
- const AUDIO_CODEC_REGEXP = /flac|opus/i;
3068
+ const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
3021
3069
  function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
3022
3070
  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
3023
3071
  }
@@ -3040,6 +3088,16 @@ function convertAVC1ToAVCOTI(codec) {
3040
3088
  }
3041
3089
  return codec;
3042
3090
  }
3091
+ function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
3092
+ const MediaSource = getMediaSource(preferManagedMediaSource) || {
3093
+ isTypeSupported: () => false
3094
+ };
3095
+ return {
3096
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
3097
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
3098
+ ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
3099
+ };
3100
+ }
3043
3101
 
3044
3102
  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;
3045
3103
  const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -3889,10 +3947,10 @@ class PlaylistLoader {
3889
3947
  const loaderContext = loader.context;
3890
3948
  if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
3891
3949
  // same URL can't overlap
3892
- logger.trace('[playlist-loader]: playlist request ongoing');
3950
+ this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
3893
3951
  return;
3894
3952
  }
3895
- logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3953
+ this.hls.logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3896
3954
  loader.abort();
3897
3955
  }
3898
3956
 
@@ -4002,7 +4060,7 @@ class PlaylistLoader {
4002
4060
  // alt audio rendition in which quality levels (main)
4003
4061
  // contains both audio+video. but with mixed audio track not signaled
4004
4062
  if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
4005
- logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
4063
+ this.hls.logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
4006
4064
  audioTracks.unshift({
4007
4065
  type: 'main',
4008
4066
  name: 'main',
@@ -4101,7 +4159,7 @@ class PlaylistLoader {
4101
4159
  message += ` id: ${context.id} group-id: "${context.groupId}"`;
4102
4160
  }
4103
4161
  const error = new Error(message);
4104
- logger.warn(`[playlist-loader]: ${message}`);
4162
+ this.hls.logger.warn(`[playlist-loader]: ${message}`);
4105
4163
  let details = ErrorDetails.UNKNOWN;
4106
4164
  let fatal = false;
4107
4165
  const loader = this.getInternalLoader(context);
@@ -4706,7 +4764,47 @@ class LatencyController {
4706
4764
  this.currentTime = 0;
4707
4765
  this.stallCount = 0;
4708
4766
  this._latency = null;
4709
- this.timeupdateHandler = () => this.timeupdate();
4767
+ this.onTimeupdate = () => {
4768
+ const {
4769
+ media,
4770
+ levelDetails
4771
+ } = this;
4772
+ if (!media || !levelDetails) {
4773
+ return;
4774
+ }
4775
+ this.currentTime = media.currentTime;
4776
+ const latency = this.computeLatency();
4777
+ if (latency === null) {
4778
+ return;
4779
+ }
4780
+ this._latency = latency;
4781
+
4782
+ // Adapt playbackRate to meet target latency in low-latency mode
4783
+ const {
4784
+ lowLatencyMode,
4785
+ maxLiveSyncPlaybackRate
4786
+ } = this.config;
4787
+ if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4788
+ return;
4789
+ }
4790
+ const targetLatency = this.targetLatency;
4791
+ if (targetLatency === null) {
4792
+ return;
4793
+ }
4794
+ const distanceFromTarget = latency - targetLatency;
4795
+ // Only adjust playbackRate when within one target duration of targetLatency
4796
+ // and more than one second from under-buffering.
4797
+ // Playback further than one target duration from target can be considered DVR playback.
4798
+ const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4799
+ const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4800
+ if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4801
+ const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4802
+ const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4803
+ media.playbackRate = Math.min(max, Math.max(1, rate));
4804
+ } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4805
+ media.playbackRate = 1;
4806
+ }
4807
+ };
4710
4808
  this.hls = hls;
4711
4809
  this.config = hls.config;
4712
4810
  this.registerListeners();
@@ -4798,7 +4896,7 @@ class LatencyController {
4798
4896
  this.onMediaDetaching();
4799
4897
  this.levelDetails = null;
4800
4898
  // @ts-ignore
4801
- this.hls = this.timeupdateHandler = null;
4899
+ this.hls = null;
4802
4900
  }
4803
4901
  registerListeners() {
4804
4902
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -4816,11 +4914,11 @@ class LatencyController {
4816
4914
  }
4817
4915
  onMediaAttached(event, data) {
4818
4916
  this.media = data.media;
4819
- this.media.addEventListener('timeupdate', this.timeupdateHandler);
4917
+ this.media.addEventListener('timeupdate', this.onTimeupdate);
4820
4918
  }
4821
4919
  onMediaDetaching() {
4822
4920
  if (this.media) {
4823
- this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4921
+ this.media.removeEventListener('timeupdate', this.onTimeupdate);
4824
4922
  this.media = null;
4825
4923
  }
4826
4924
  }
@@ -4834,10 +4932,10 @@ class LatencyController {
4834
4932
  }) {
4835
4933
  this.levelDetails = details;
4836
4934
  if (details.advanced) {
4837
- this.timeupdate();
4935
+ this.onTimeupdate();
4838
4936
  }
4839
4937
  if (!details.live && this.media) {
4840
- this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4938
+ this.media.removeEventListener('timeupdate', this.onTimeupdate);
4841
4939
  }
4842
4940
  }
4843
4941
  onError(event, data) {
@@ -4847,48 +4945,7 @@ class LatencyController {
4847
4945
  }
4848
4946
  this.stallCount++;
4849
4947
  if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
4850
- logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
4851
- }
4852
- }
4853
- timeupdate() {
4854
- const {
4855
- media,
4856
- levelDetails
4857
- } = this;
4858
- if (!media || !levelDetails) {
4859
- return;
4860
- }
4861
- this.currentTime = media.currentTime;
4862
- const latency = this.computeLatency();
4863
- if (latency === null) {
4864
- return;
4865
- }
4866
- this._latency = latency;
4867
-
4868
- // Adapt playbackRate to meet target latency in low-latency mode
4869
- const {
4870
- lowLatencyMode,
4871
- maxLiveSyncPlaybackRate
4872
- } = this.config;
4873
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4874
- return;
4875
- }
4876
- const targetLatency = this.targetLatency;
4877
- if (targetLatency === null) {
4878
- return;
4879
- }
4880
- const distanceFromTarget = latency - targetLatency;
4881
- // Only adjust playbackRate when within one target duration of targetLatency
4882
- // and more than one second from under-buffering.
4883
- // Playback further than one target duration from target can be considered DVR playback.
4884
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4885
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4886
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4887
- const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4888
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4889
- media.playbackRate = Math.min(max, Math.max(1, rate));
4890
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4891
- media.playbackRate = 1;
4948
+ this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
4892
4949
  }
4893
4950
  }
4894
4951
  estimateLiveEdge() {
@@ -5660,18 +5717,13 @@ var ErrorActionFlags = {
5660
5717
  MoveAllAlternatesMatchingHDCP: 2,
5661
5718
  SwitchToSDR: 4
5662
5719
  }; // Reserved for future use
5663
- class ErrorController {
5720
+ class ErrorController extends Logger {
5664
5721
  constructor(hls) {
5722
+ super('error-controller', hls.logger);
5665
5723
  this.hls = void 0;
5666
5724
  this.playlistError = 0;
5667
5725
  this.penalizedRenditions = {};
5668
- this.log = void 0;
5669
- this.warn = void 0;
5670
- this.error = void 0;
5671
5726
  this.hls = hls;
5672
- this.log = logger.log.bind(logger, `[info]:`);
5673
- this.warn = logger.warn.bind(logger, `[warning]:`);
5674
- this.error = logger.error.bind(logger, `[error]:`);
5675
5727
  this.registerListeners();
5676
5728
  }
5677
5729
  registerListeners() {
@@ -6023,16 +6075,13 @@ class ErrorController {
6023
6075
  }
6024
6076
  }
6025
6077
 
6026
- class BasePlaylistController {
6078
+ class BasePlaylistController extends Logger {
6027
6079
  constructor(hls, logPrefix) {
6080
+ super(logPrefix, hls.logger);
6028
6081
  this.hls = void 0;
6029
6082
  this.timer = -1;
6030
6083
  this.requestScheduled = -1;
6031
6084
  this.canLoad = false;
6032
- this.log = void 0;
6033
- this.warn = void 0;
6034
- this.log = logger.log.bind(logger, `${logPrefix}:`);
6035
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
6036
6085
  this.hls = hls;
6037
6086
  }
6038
6087
  destroy() {
@@ -6065,7 +6114,7 @@ class BasePlaylistController {
6065
6114
  try {
6066
6115
  uri = new self.URL(attr.URI, previous.url).href;
6067
6116
  } catch (error) {
6068
- logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
6117
+ this.warn(`Could not construct new URL for Rendition Report: ${error}`);
6069
6118
  uri = attr.URI || '';
6070
6119
  }
6071
6120
  // Use exact match. Otherwise, the last partial match, if any, will be used
@@ -6152,7 +6201,12 @@ class BasePlaylistController {
6152
6201
  const cdnAge = lastAdvanced + details.ageHeader;
6153
6202
  let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
6154
6203
  if (currentGoal > 0) {
6155
- if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
6204
+ if (cdnAge > details.targetduration * 3) {
6205
+ // Omit segment and part directives when the last response was more than 3 target durations ago,
6206
+ this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
6207
+ msn = undefined;
6208
+ part = undefined;
6209
+ } else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
6156
6210
  // If we attempted to get the next or latest playlist update, but currentGoal increased,
6157
6211
  // then we either can't catchup, or the "age" header cannot be trusted.
6158
6212
  this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
@@ -6824,8 +6878,9 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
6824
6878
  return -1;
6825
6879
  }
6826
6880
 
6827
- class AbrController {
6881
+ class AbrController extends Logger {
6828
6882
  constructor(_hls) {
6883
+ super('abr', _hls.logger);
6829
6884
  this.hls = void 0;
6830
6885
  this.lastLevelLoadSec = 0;
6831
6886
  this.lastLoadedFragLevel = -1;
@@ -6939,7 +6994,7 @@ class AbrController {
6939
6994
  this.resetEstimator(nextLoadLevelBitrate);
6940
6995
  }
6941
6996
  this.clearTimer();
6942
- logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6997
+ this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6943
6998
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
6944
6999
  Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
6945
7000
  Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
@@ -6959,7 +7014,7 @@ class AbrController {
6959
7014
  }
6960
7015
  resetEstimator(abrEwmaDefaultEstimate) {
6961
7016
  if (abrEwmaDefaultEstimate) {
6962
- logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
7017
+ this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6963
7018
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
6964
7019
  }
6965
7020
  this.firstSelection = -1;
@@ -7191,7 +7246,7 @@ class AbrController {
7191
7246
  }
7192
7247
  const firstLevel = this.hls.firstLevel;
7193
7248
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
7194
- logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7249
+ this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7195
7250
  return clamped;
7196
7251
  }
7197
7252
  get forcedAutoLevel() {
@@ -7237,6 +7292,9 @@ class AbrController {
7237
7292
  partCurrent,
7238
7293
  hls
7239
7294
  } = this;
7295
+ if (hls.levels.length <= 1) {
7296
+ return hls.loadLevel;
7297
+ }
7240
7298
  const {
7241
7299
  maxAutoLevel,
7242
7300
  config,
@@ -7269,13 +7327,13 @@ class AbrController {
7269
7327
  // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
7270
7328
  const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
7271
7329
  maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
7272
- logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7330
+ this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7273
7331
  // don't use conservative factor on bitrate test
7274
7332
  bwFactor = bwUpFactor = 1;
7275
7333
  }
7276
7334
  }
7277
7335
  const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
7278
- logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7336
+ this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7279
7337
  if (bestLevel > -1) {
7280
7338
  return bestLevel;
7281
7339
  }
@@ -7349,7 +7407,7 @@ class AbrController {
7349
7407
  currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
7350
7408
  currentFrameRate = minFramerate;
7351
7409
  currentBw = Math.max(currentBw, minBitrate);
7352
- logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
7410
+ this.log(`picked start tier ${JSON.stringify(startTier)}`);
7353
7411
  } else {
7354
7412
  currentCodecSet = level == null ? void 0 : level.codecSet;
7355
7413
  currentVideoRange = level == null ? void 0 : level.videoRange;
@@ -7376,11 +7434,11 @@ class AbrController {
7376
7434
  const levels = this.hls.levels;
7377
7435
  const index = levels.indexOf(levelInfo);
7378
7436
  if (decodingInfo.error) {
7379
- logger.warn(`[abr] MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7437
+ this.warn(`MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7380
7438
  } else if (!decodingInfo.supported) {
7381
- logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7439
+ this.warn(`Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7382
7440
  if (index > -1 && levels.length > 1) {
7383
- logger.log(`[abr] Removing unsupported level ${index}`);
7441
+ this.log(`Removing unsupported level ${index}`);
7384
7442
  this.hls.removeLevel(index);
7385
7443
  }
7386
7444
  }
@@ -7427,9 +7485,9 @@ class AbrController {
7427
7485
  const forcedAutoLevel = this.forcedAutoLevel;
7428
7486
  if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
7429
7487
  if (levelsSkipped.length) {
7430
- 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}`);
7488
+ 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}`);
7431
7489
  }
7432
- 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}`);
7490
+ 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}`);
7433
7491
  }
7434
7492
  if (firstSelection) {
7435
7493
  this.firstSelection = i;
@@ -7483,8 +7541,9 @@ class AbrController {
7483
7541
  * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
7484
7542
  * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
7485
7543
  */
7486
- class TaskLoop {
7487
- constructor() {
7544
+ class TaskLoop extends Logger {
7545
+ constructor(label, logger) {
7546
+ super(label, logger);
7488
7547
  this._boundTick = void 0;
7489
7548
  this._tickTimer = null;
7490
7549
  this._tickInterval = null;
@@ -7646,13 +7705,16 @@ class FragmentTracker {
7646
7705
  * If not found any Fragment, return null
7647
7706
  */
7648
7707
  getBufferedFrag(position, levelType) {
7708
+ return this.getFragAtPos(position, levelType, true);
7709
+ }
7710
+ getFragAtPos(position, levelType, buffered) {
7649
7711
  const {
7650
7712
  fragments
7651
7713
  } = this;
7652
7714
  const keys = Object.keys(fragments);
7653
7715
  for (let i = keys.length; i--;) {
7654
7716
  const fragmentEntity = fragments[keys[i]];
7655
- if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && fragmentEntity.buffered) {
7717
+ if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && (!buffered || fragmentEntity.buffered)) {
7656
7718
  const frag = fragmentEntity.body;
7657
7719
  if (frag.start <= position && position <= frag.end) {
7658
7720
  return frag;
@@ -7907,7 +7969,8 @@ class FragmentTracker {
7907
7969
  const {
7908
7970
  frag,
7909
7971
  part,
7910
- timeRanges
7972
+ timeRanges,
7973
+ type
7911
7974
  } = data;
7912
7975
  if (frag.sn === 'initSegment') {
7913
7976
  return;
@@ -7922,10 +7985,8 @@ class FragmentTracker {
7922
7985
  }
7923
7986
  // Store the latest timeRanges loaded in the buffer
7924
7987
  this.timeRanges = timeRanges;
7925
- Object.keys(timeRanges).forEach(elementaryStream => {
7926
- const timeRange = timeRanges[elementaryStream];
7927
- this.detectEvictedFragments(elementaryStream, timeRange, playlistType, part);
7928
- });
7988
+ const timeRange = timeRanges[type];
7989
+ this.detectEvictedFragments(type, timeRange, playlistType, part);
7929
7990
  }
7930
7991
  onFragBuffered(event, data) {
7931
7992
  this.detectPartialFragments(data);
@@ -8003,40 +8064,29 @@ class BufferHelper {
8003
8064
  * Return true if `media`'s buffered include `position`
8004
8065
  */
8005
8066
  static isBuffered(media, position) {
8006
- try {
8007
- if (media) {
8008
- const buffered = BufferHelper.getBuffered(media);
8009
- for (let i = 0; i < buffered.length; i++) {
8010
- if (position >= buffered.start(i) && position <= buffered.end(i)) {
8011
- return true;
8012
- }
8067
+ if (media) {
8068
+ const buffered = BufferHelper.getBuffered(media);
8069
+ for (let i = buffered.length; i--;) {
8070
+ if (position >= buffered.start(i) && position <= buffered.end(i)) {
8071
+ return true;
8013
8072
  }
8014
8073
  }
8015
- } catch (error) {
8016
- // this is to catch
8017
- // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
8018
- // This SourceBuffer has been removed from the parent media source
8019
8074
  }
8020
8075
  return false;
8021
8076
  }
8022
8077
  static bufferInfo(media, pos, maxHoleDuration) {
8023
- try {
8024
- if (media) {
8025
- const vbuffered = BufferHelper.getBuffered(media);
8078
+ if (media) {
8079
+ const vbuffered = BufferHelper.getBuffered(media);
8080
+ if (vbuffered.length) {
8026
8081
  const buffered = [];
8027
- let i;
8028
- for (i = 0; i < vbuffered.length; i++) {
8082
+ for (let i = 0; i < vbuffered.length; i++) {
8029
8083
  buffered.push({
8030
8084
  start: vbuffered.start(i),
8031
8085
  end: vbuffered.end(i)
8032
8086
  });
8033
8087
  }
8034
- return this.bufferedInfo(buffered, pos, maxHoleDuration);
8088
+ return BufferHelper.bufferedInfo(buffered, pos, maxHoleDuration);
8035
8089
  }
8036
- } catch (error) {
8037
- // this is to catch
8038
- // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
8039
- // This SourceBuffer has been removed from the parent media source
8040
8090
  }
8041
8091
  return {
8042
8092
  len: 0,
@@ -8048,14 +8098,7 @@ class BufferHelper {
8048
8098
  static bufferedInfo(buffered, pos, maxHoleDuration) {
8049
8099
  pos = Math.max(0, pos);
8050
8100
  // sort on buffer.start/smaller end (IE does not always return sorted buffered range)
8051
- buffered.sort(function (a, b) {
8052
- const diff = a.start - b.start;
8053
- if (diff) {
8054
- return diff;
8055
- } else {
8056
- return b.end - a.end;
8057
- }
8058
- });
8101
+ buffered.sort((a, b) => a.start - b.start || b.end - a.end);
8059
8102
  let buffered2 = [];
8060
8103
  if (maxHoleDuration) {
8061
8104
  // there might be some small holes between buffer time range
@@ -8122,7 +8165,7 @@ class BufferHelper {
8122
8165
  */
8123
8166
  static getBuffered(media) {
8124
8167
  try {
8125
- return media.buffered;
8168
+ return media.buffered || noopBuffered;
8126
8169
  } catch (e) {
8127
8170
  logger.log('failed to get media.buffered', e);
8128
8171
  return noopBuffered;
@@ -8575,8 +8618,8 @@ function createLoaderContext(frag, part = null) {
8575
8618
  var _frag$decryptdata;
8576
8619
  let byteRangeStart = start;
8577
8620
  let byteRangeEnd = end;
8578
- if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
8579
- // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
8621
+ if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
8622
+ // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
8580
8623
  // has the unencrypted size specified in the range.
8581
8624
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
8582
8625
  const fragmentLen = end - start;
@@ -8609,6 +8652,9 @@ function createGapLoadError(frag, part) {
8609
8652
  (part ? part : frag).stats.aborted = true;
8610
8653
  return new LoadError(errorData);
8611
8654
  }
8655
+ function isMethodFullSegmentAesCbc(method) {
8656
+ return method === 'AES-128' || method === 'AES-256';
8657
+ }
8612
8658
  class LoadError extends Error {
8613
8659
  constructor(data) {
8614
8660
  super(data.error.message);
@@ -8618,33 +8664,61 @@ class LoadError extends Error {
8618
8664
  }
8619
8665
 
8620
8666
  class AESCrypto {
8621
- constructor(subtle, iv) {
8667
+ constructor(subtle, iv, aesMode) {
8622
8668
  this.subtle = void 0;
8623
8669
  this.aesIV = void 0;
8670
+ this.aesMode = void 0;
8624
8671
  this.subtle = subtle;
8625
8672
  this.aesIV = iv;
8673
+ this.aesMode = aesMode;
8626
8674
  }
8627
8675
  decrypt(data, key) {
8628
- return this.subtle.decrypt({
8629
- name: 'AES-CBC',
8630
- iv: this.aesIV
8631
- }, key, data);
8676
+ switch (this.aesMode) {
8677
+ case DecrypterAesMode.cbc:
8678
+ return this.subtle.decrypt({
8679
+ name: 'AES-CBC',
8680
+ iv: this.aesIV
8681
+ }, key, data);
8682
+ case DecrypterAesMode.ctr:
8683
+ return this.subtle.decrypt({
8684
+ name: 'AES-CTR',
8685
+ counter: this.aesIV,
8686
+ length: 64
8687
+ },
8688
+ //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
8689
+ key, data);
8690
+ default:
8691
+ throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
8692
+ }
8632
8693
  }
8633
8694
  }
8634
8695
 
8635
8696
  class FastAESKey {
8636
- constructor(subtle, key) {
8697
+ constructor(subtle, key, aesMode) {
8637
8698
  this.subtle = void 0;
8638
8699
  this.key = void 0;
8700
+ this.aesMode = void 0;
8639
8701
  this.subtle = subtle;
8640
8702
  this.key = key;
8703
+ this.aesMode = aesMode;
8641
8704
  }
8642
8705
  expandKey() {
8706
+ const subtleAlgoName = getSubtleAlgoName(this.aesMode);
8643
8707
  return this.subtle.importKey('raw', this.key, {
8644
- name: 'AES-CBC'
8708
+ name: subtleAlgoName
8645
8709
  }, false, ['encrypt', 'decrypt']);
8646
8710
  }
8647
8711
  }
8712
+ function getSubtleAlgoName(aesMode) {
8713
+ switch (aesMode) {
8714
+ case DecrypterAesMode.cbc:
8715
+ return 'AES-CBC';
8716
+ case DecrypterAesMode.ctr:
8717
+ return 'AES-CTR';
8718
+ default:
8719
+ throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
8720
+ }
8721
+ }
8648
8722
 
8649
8723
  // PKCS7
8650
8724
  function removePadding(array) {
@@ -8894,7 +8968,8 @@ class Decrypter {
8894
8968
  this.currentIV = null;
8895
8969
  this.currentResult = null;
8896
8970
  this.useSoftware = void 0;
8897
- this.useSoftware = config.enableSoftwareAES;
8971
+ this.enableSoftwareAES = void 0;
8972
+ this.enableSoftwareAES = config.enableSoftwareAES;
8898
8973
  this.removePKCS7Padding = removePKCS7Padding;
8899
8974
  // built in decryptor expects PKCS7 padding
8900
8975
  if (removePKCS7Padding) {
@@ -8907,9 +8982,7 @@ class Decrypter {
8907
8982
  /* no-op */
8908
8983
  }
8909
8984
  }
8910
- if (this.subtle === null) {
8911
- this.useSoftware = true;
8912
- }
8985
+ this.useSoftware = this.subtle === null;
8913
8986
  }
8914
8987
  destroy() {
8915
8988
  this.subtle = null;
@@ -8947,10 +9020,10 @@ class Decrypter {
8947
9020
  this.softwareDecrypter = null;
8948
9021
  }
8949
9022
  }
8950
- decrypt(data, key, iv) {
9023
+ decrypt(data, key, iv, aesMode) {
8951
9024
  if (this.useSoftware) {
8952
9025
  return new Promise((resolve, reject) => {
8953
- this.softwareDecrypt(new Uint8Array(data), key, iv);
9026
+ this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
8954
9027
  const decryptResult = this.flush();
8955
9028
  if (decryptResult) {
8956
9029
  resolve(decryptResult.buffer);
@@ -8959,17 +9032,21 @@ class Decrypter {
8959
9032
  }
8960
9033
  });
8961
9034
  }
8962
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
9035
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
8963
9036
  }
8964
9037
 
8965
9038
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
8966
9039
  // data is handled in the flush() call
8967
- softwareDecrypt(data, key, iv) {
9040
+ softwareDecrypt(data, key, iv, aesMode) {
8968
9041
  const {
8969
9042
  currentIV,
8970
9043
  currentResult,
8971
9044
  remainderData
8972
9045
  } = this;
9046
+ if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
9047
+ logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
9048
+ return null;
9049
+ }
8973
9050
  this.logOnce('JS AES decrypt');
8974
9051
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
8975
9052
  // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
@@ -9002,11 +9079,11 @@ class Decrypter {
9002
9079
  }
9003
9080
  return result;
9004
9081
  }
9005
- webCryptoDecrypt(data, key, iv) {
9082
+ webCryptoDecrypt(data, key, iv, aesMode) {
9006
9083
  const subtle = this.subtle;
9007
9084
  if (this.key !== key || !this.fastAesKey) {
9008
9085
  this.key = key;
9009
- this.fastAesKey = new FastAESKey(subtle, key);
9086
+ this.fastAesKey = new FastAESKey(subtle, key, aesMode);
9010
9087
  }
9011
9088
  return this.fastAesKey.expandKey().then(aesKey => {
9012
9089
  // decrypt using web crypto
@@ -9014,22 +9091,25 @@ class Decrypter {
9014
9091
  return Promise.reject(new Error('web crypto not initialized'));
9015
9092
  }
9016
9093
  this.logOnce('WebCrypto AES decrypt');
9017
- const crypto = new AESCrypto(subtle, new Uint8Array(iv));
9094
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
9018
9095
  return crypto.decrypt(data.buffer, aesKey);
9019
9096
  }).catch(err => {
9020
9097
  logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
9021
- return this.onWebCryptoError(data, key, iv);
9098
+ return this.onWebCryptoError(data, key, iv, aesMode);
9022
9099
  });
9023
9100
  }
9024
- onWebCryptoError(data, key, iv) {
9025
- this.useSoftware = true;
9026
- this.logEnabled = true;
9027
- this.softwareDecrypt(data, key, iv);
9028
- const decryptResult = this.flush();
9029
- if (decryptResult) {
9030
- return decryptResult.buffer;
9101
+ onWebCryptoError(data, key, iv, aesMode) {
9102
+ const enableSoftwareAES = this.enableSoftwareAES;
9103
+ if (enableSoftwareAES) {
9104
+ this.useSoftware = true;
9105
+ this.logEnabled = true;
9106
+ this.softwareDecrypt(data, key, iv, aesMode);
9107
+ const decryptResult = this.flush();
9108
+ if (decryptResult) {
9109
+ return decryptResult.buffer;
9110
+ }
9031
9111
  }
9032
- throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
9112
+ throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
9033
9113
  }
9034
9114
  getValidChunk(data) {
9035
9115
  let currentChunk = data;
@@ -9080,7 +9160,7 @@ const State = {
9080
9160
  };
9081
9161
  class BaseStreamController extends TaskLoop {
9082
9162
  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
9083
- super();
9163
+ super(logPrefix, hls.logger);
9084
9164
  this.hls = void 0;
9085
9165
  this.fragPrevious = null;
9086
9166
  this.fragCurrent = null;
@@ -9105,22 +9185,98 @@ class BaseStreamController extends TaskLoop {
9105
9185
  this.startFragRequested = false;
9106
9186
  this.decrypter = void 0;
9107
9187
  this.initPTS = [];
9108
- this.onvseeking = null;
9109
- this.onvended = null;
9110
- this.logPrefix = '';
9111
- this.log = void 0;
9112
- this.warn = void 0;
9188
+ this.buffering = true;
9189
+ this.loadingParts = false;
9190
+ this.onMediaSeeking = () => {
9191
+ const {
9192
+ config,
9193
+ fragCurrent,
9194
+ media,
9195
+ mediaBuffer,
9196
+ state
9197
+ } = this;
9198
+ const currentTime = media ? media.currentTime : 0;
9199
+ const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9200
+ this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9201
+ if (this.state === State.ENDED) {
9202
+ this.resetLoadingState();
9203
+ } else if (fragCurrent) {
9204
+ // Seeking while frag load is in progress
9205
+ const tolerance = config.maxFragLookUpTolerance;
9206
+ const fragStartOffset = fragCurrent.start - tolerance;
9207
+ const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9208
+ // if seeking out of buffered range or into new one
9209
+ if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9210
+ const pastFragment = currentTime > fragEndOffset;
9211
+ // if the seek position is outside the current fragment range
9212
+ if (currentTime < fragStartOffset || pastFragment) {
9213
+ if (pastFragment && fragCurrent.loader) {
9214
+ this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9215
+ fragCurrent.abortRequests();
9216
+ this.resetLoadingState();
9217
+ }
9218
+ this.fragPrevious = null;
9219
+ }
9220
+ }
9221
+ }
9222
+ if (media) {
9223
+ // Remove gap fragments
9224
+ this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9225
+ this.lastCurrentTime = currentTime;
9226
+ if (!this.loadingParts) {
9227
+ const bufferEnd = Math.max(bufferInfo.end, currentTime);
9228
+ const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
9229
+ if (shouldLoadParts) {
9230
+ this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
9231
+ this.loadingParts = shouldLoadParts;
9232
+ }
9233
+ }
9234
+ }
9235
+
9236
+ // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9237
+ if (!this.loadedmetadata && !bufferInfo.len) {
9238
+ this.nextLoadPosition = this.startPosition = currentTime;
9239
+ }
9240
+
9241
+ // Async tick to speed up processing
9242
+ this.tickImmediate();
9243
+ };
9244
+ this.onMediaEnded = () => {
9245
+ // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9246
+ this.startPosition = this.lastCurrentTime = 0;
9247
+ if (this.playlistType === PlaylistLevelType.MAIN) {
9248
+ this.hls.trigger(Events.MEDIA_ENDED, {
9249
+ stalled: false
9250
+ });
9251
+ }
9252
+ };
9113
9253
  this.playlistType = playlistType;
9114
- this.logPrefix = logPrefix;
9115
- this.log = logger.log.bind(logger, `${logPrefix}:`);
9116
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
9117
9254
  this.hls = hls;
9118
9255
  this.fragmentLoader = new FragmentLoader(hls.config);
9119
9256
  this.keyLoader = keyLoader;
9120
9257
  this.fragmentTracker = fragmentTracker;
9121
9258
  this.config = hls.config;
9122
9259
  this.decrypter = new Decrypter(hls.config);
9260
+ }
9261
+ registerListeners() {
9262
+ const {
9263
+ hls
9264
+ } = this;
9265
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
9266
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
9267
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
9123
9268
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9269
+ hls.on(Events.ERROR, this.onError, this);
9270
+ }
9271
+ unregisterListeners() {
9272
+ const {
9273
+ hls
9274
+ } = this;
9275
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
9276
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
9277
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
9278
+ hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9279
+ hls.off(Events.ERROR, this.onError, this);
9124
9280
  }
9125
9281
  doTick() {
9126
9282
  this.onTickEnd();
@@ -9144,6 +9300,12 @@ class BaseStreamController extends TaskLoop {
9144
9300
  this.clearNextTick();
9145
9301
  this.state = State.STOPPED;
9146
9302
  }
9303
+ pauseBuffering() {
9304
+ this.buffering = false;
9305
+ }
9306
+ resumeBuffering() {
9307
+ this.buffering = true;
9308
+ }
9147
9309
  _streamEnded(bufferInfo, levelDetails) {
9148
9310
  // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
9149
9311
  // of nothing loading/loaded return false
@@ -9174,10 +9336,8 @@ class BaseStreamController extends TaskLoop {
9174
9336
  }
9175
9337
  onMediaAttached(event, data) {
9176
9338
  const media = this.media = this.mediaBuffer = data.media;
9177
- this.onvseeking = this.onMediaSeeking.bind(this);
9178
- this.onvended = this.onMediaEnded.bind(this);
9179
- media.addEventListener('seeking', this.onvseeking);
9180
- media.addEventListener('ended', this.onvended);
9339
+ media.addEventListener('seeking', this.onMediaSeeking);
9340
+ media.addEventListener('ended', this.onMediaEnded);
9181
9341
  const config = this.config;
9182
9342
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
9183
9343
  this.startLoad(config.startPosition);
@@ -9191,10 +9351,9 @@ class BaseStreamController extends TaskLoop {
9191
9351
  }
9192
9352
 
9193
9353
  // remove video listeners
9194
- if (media && this.onvseeking && this.onvended) {
9195
- media.removeEventListener('seeking', this.onvseeking);
9196
- media.removeEventListener('ended', this.onvended);
9197
- this.onvseeking = this.onvended = null;
9354
+ if (media) {
9355
+ media.removeEventListener('seeking', this.onMediaSeeking);
9356
+ media.removeEventListener('ended', this.onMediaEnded);
9198
9357
  }
9199
9358
  if (this.keyLoader) {
9200
9359
  this.keyLoader.detach();
@@ -9204,56 +9363,8 @@ class BaseStreamController extends TaskLoop {
9204
9363
  this.fragmentTracker.removeAllFragments();
9205
9364
  this.stopLoad();
9206
9365
  }
9207
- onMediaSeeking() {
9208
- const {
9209
- config,
9210
- fragCurrent,
9211
- media,
9212
- mediaBuffer,
9213
- state
9214
- } = this;
9215
- const currentTime = media ? media.currentTime : 0;
9216
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9217
- this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9218
- if (this.state === State.ENDED) {
9219
- this.resetLoadingState();
9220
- } else if (fragCurrent) {
9221
- // Seeking while frag load is in progress
9222
- const tolerance = config.maxFragLookUpTolerance;
9223
- const fragStartOffset = fragCurrent.start - tolerance;
9224
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9225
- // if seeking out of buffered range or into new one
9226
- if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9227
- const pastFragment = currentTime > fragEndOffset;
9228
- // if the seek position is outside the current fragment range
9229
- if (currentTime < fragStartOffset || pastFragment) {
9230
- if (pastFragment && fragCurrent.loader) {
9231
- this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9232
- fragCurrent.abortRequests();
9233
- this.resetLoadingState();
9234
- }
9235
- this.fragPrevious = null;
9236
- }
9237
- }
9238
- }
9239
- if (media) {
9240
- // Remove gap fragments
9241
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9242
- this.lastCurrentTime = currentTime;
9243
- }
9244
-
9245
- // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9246
- if (!this.loadedmetadata && !bufferInfo.len) {
9247
- this.nextLoadPosition = this.startPosition = currentTime;
9248
- }
9249
-
9250
- // Async tick to speed up processing
9251
- this.tickImmediate();
9252
- }
9253
- onMediaEnded() {
9254
- // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9255
- this.startPosition = this.lastCurrentTime = 0;
9256
- }
9366
+ onManifestLoading() {}
9367
+ onError(event, data) {}
9257
9368
  onManifestLoaded(event, data) {
9258
9369
  this.startTimeOffset = data.startTimeOffset;
9259
9370
  this.initPTS = [];
@@ -9263,7 +9374,7 @@ class BaseStreamController extends TaskLoop {
9263
9374
  this.stopLoad();
9264
9375
  super.onHandlerDestroying();
9265
9376
  // @ts-ignore
9266
- this.hls = null;
9377
+ this.hls = this.onMediaSeeking = this.onMediaEnded = null;
9267
9378
  }
9268
9379
  onHandlerDestroyed() {
9269
9380
  this.state = State.STOPPED;
@@ -9394,10 +9505,10 @@ class BaseStreamController extends TaskLoop {
9394
9505
  const decryptData = frag.decryptdata;
9395
9506
 
9396
9507
  // check to see if the payload needs to be decrypted
9397
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
9508
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
9398
9509
  const startTime = self.performance.now();
9399
9510
  // decrypt init segment data
9400
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
9511
+ return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
9401
9512
  hls.trigger(Events.ERROR, {
9402
9513
  type: ErrorTypes.MEDIA_ERROR,
9403
9514
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -9509,7 +9620,7 @@ class BaseStreamController extends TaskLoop {
9509
9620
  }
9510
9621
  let keyLoadingPromise = null;
9511
9622
  if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
9512
- this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
9623
+ this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
9513
9624
  this.state = State.KEY_LOADING;
9514
9625
  this.fragCurrent = frag;
9515
9626
  keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
@@ -9530,8 +9641,16 @@ class BaseStreamController extends TaskLoop {
9530
9641
  } else if (!frag.encrypted && details.encryptedFragments.length) {
9531
9642
  this.keyLoader.loadClear(frag, details.encryptedFragments);
9532
9643
  }
9644
+ const fragPrevious = this.fragPrevious;
9645
+ if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
9646
+ const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
9647
+ if (shouldLoadParts !== this.loadingParts) {
9648
+ this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
9649
+ this.loadingParts = shouldLoadParts;
9650
+ }
9651
+ }
9533
9652
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
9534
- if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
9653
+ if (this.loadingParts && frag.sn !== 'initSegment') {
9535
9654
  const partList = details.partList;
9536
9655
  if (partList && progressCallback) {
9537
9656
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -9540,7 +9659,7 @@ class BaseStreamController extends TaskLoop {
9540
9659
  const partIndex = this.getNextPart(partList, frag, targetBufferTime);
9541
9660
  if (partIndex > -1) {
9542
9661
  const part = partList[partIndex];
9543
- 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))}`);
9662
+ 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))}`);
9544
9663
  this.nextLoadPosition = part.start + part.duration;
9545
9664
  this.state = State.FRAG_LOADING;
9546
9665
  let _result;
@@ -9569,7 +9688,14 @@ class BaseStreamController extends TaskLoop {
9569
9688
  }
9570
9689
  }
9571
9690
  }
9572
- 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))}`);
9691
+ if (frag.sn !== 'initSegment' && this.loadingParts) {
9692
+ this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
9693
+ this.loadingParts = false;
9694
+ } else if (!frag.url) {
9695
+ // Selected fragment hint for part but not loading parts
9696
+ return Promise.resolve(null);
9697
+ }
9698
+ 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))}`);
9573
9699
  // Don't update nextLoadPosition for fragments which are not buffered
9574
9700
  if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
9575
9701
  this.nextLoadPosition = frag.start + frag.duration;
@@ -9667,12 +9793,40 @@ class BaseStreamController extends TaskLoop {
9667
9793
  if (part) {
9668
9794
  part.stats.parsing.end = now;
9669
9795
  }
9796
+ // See if part loading should be disabled/enabled based on buffer and playback position.
9797
+ if (frag.sn !== 'initSegment') {
9798
+ const levelDetails = this.getLevelDetails();
9799
+ const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
9800
+ const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
9801
+ if (shouldLoadParts !== this.loadingParts) {
9802
+ this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
9803
+ this.loadingParts = shouldLoadParts;
9804
+ }
9805
+ }
9670
9806
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
9671
9807
  }
9672
- getCurrentContext(chunkMeta) {
9673
- const {
9674
- levels,
9675
- fragCurrent
9808
+ shouldLoadParts(details, bufferEnd) {
9809
+ if (this.config.lowLatencyMode) {
9810
+ if (!details) {
9811
+ return this.loadingParts;
9812
+ }
9813
+ if (details != null && details.partList) {
9814
+ var _details$fragmentHint;
9815
+ // Buffer must be ahead of first part + duration of parts after last segment
9816
+ // and playback must be at or past segment adjacent to part list
9817
+ const firstPart = details.partList[0];
9818
+ const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
9819
+ if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
9820
+ return true;
9821
+ }
9822
+ }
9823
+ }
9824
+ return false;
9825
+ }
9826
+ getCurrentContext(chunkMeta) {
9827
+ const {
9828
+ levels,
9829
+ fragCurrent
9676
9830
  } = this;
9677
9831
  const {
9678
9832
  level: levelIndex,
@@ -9769,7 +9923,7 @@ class BaseStreamController extends TaskLoop {
9769
9923
  // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
9770
9924
  if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
9771
9925
  const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
9772
- if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
9926
+ if (bufferedFragAtPos && (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)) {
9773
9927
  return BufferHelper.bufferInfo(bufferable, pos, Math.max(bufferInfo.nextStart, maxBufferHole));
9774
9928
  }
9775
9929
  }
@@ -9817,7 +9971,8 @@ class BaseStreamController extends TaskLoop {
9817
9971
  config
9818
9972
  } = this;
9819
9973
  const start = fragments[0].start;
9820
- let frag;
9974
+ const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
9975
+ let frag = null;
9821
9976
  if (levelDetails.live) {
9822
9977
  const initialLiveManifestSize = config.initialLiveManifestSize;
9823
9978
  if (fragLen < initialLiveManifestSize) {
@@ -9829,6 +9984,10 @@ class BaseStreamController extends TaskLoop {
9829
9984
  // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
9830
9985
  // we get the fragment matching that start time
9831
9986
  if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
9987
+ if (canLoadParts && !this.loadingParts) {
9988
+ this.log(`LL-Part loading ON for initial live fragment`);
9989
+ this.loadingParts = true;
9990
+ }
9832
9991
  frag = this.getInitialLiveFragment(levelDetails, fragments);
9833
9992
  this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
9834
9993
  }
@@ -9839,7 +9998,7 @@ class BaseStreamController extends TaskLoop {
9839
9998
 
9840
9999
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
9841
10000
  if (!frag) {
9842
- const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
10001
+ const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
9843
10002
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
9844
10003
  }
9845
10004
  return this.mapToInitFragWhenRequired(frag);
@@ -9961,7 +10120,7 @@ class BaseStreamController extends TaskLoop {
9961
10120
  } = levelDetails;
9962
10121
  const tolerance = config.maxFragLookUpTolerance;
9963
10122
  const partList = levelDetails.partList;
9964
- const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
10123
+ const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
9965
10124
  if (loadingParts && fragmentHint && !this.bitrateTest) {
9966
10125
  // Include incomplete fragment with parts at end
9967
10126
  fragments = fragments.concat(fragmentHint);
@@ -10154,7 +10313,7 @@ class BaseStreamController extends TaskLoop {
10154
10313
  errorAction.resolved = true;
10155
10314
  }
10156
10315
  } else {
10157
- logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10316
+ this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10158
10317
  return;
10159
10318
  }
10160
10319
  } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
@@ -10222,7 +10381,9 @@ class BaseStreamController extends TaskLoop {
10222
10381
  this.log('Reset loading state');
10223
10382
  this.fragCurrent = null;
10224
10383
  this.fragPrevious = null;
10225
- this.state = State.IDLE;
10384
+ if (this.state !== State.STOPPED) {
10385
+ this.state = State.IDLE;
10386
+ }
10226
10387
  }
10227
10388
  resetStartWhenNotLoaded(level) {
10228
10389
  // if loadedmetadata is not set, it means that first frag request failed
@@ -10563,6 +10724,7 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
10563
10724
  */
10564
10725
  function getAudioConfig(observer, data, offset, audioCodec) {
10565
10726
  let adtsObjectType;
10727
+ let originalAdtsObjectType;
10566
10728
  let adtsExtensionSamplingIndex;
10567
10729
  let adtsChannelConfig;
10568
10730
  let config;
@@ -10570,7 +10732,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10570
10732
  const manifestCodec = audioCodec;
10571
10733
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
10572
10734
  // byte 2
10573
- adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10735
+ adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10574
10736
  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
10575
10737
  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
10576
10738
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
@@ -10587,8 +10749,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10587
10749
  // byte 3
10588
10750
  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
10589
10751
  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
10590
- // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
10591
- if (/firefox/i.test(userAgent)) {
10752
+ // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
10753
+ if (/firefox|palemoon/i.test(userAgent)) {
10592
10754
  if (adtsSamplingIndex >= 6) {
10593
10755
  adtsObjectType = 5;
10594
10756
  config = new Array(4);
@@ -10682,6 +10844,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10682
10844
  samplerate: adtsSamplingRates[adtsSamplingIndex],
10683
10845
  channelCount: adtsChannelConfig,
10684
10846
  codec: 'mp4a.40.' + adtsObjectType,
10847
+ parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
10685
10848
  manifestCodec
10686
10849
  };
10687
10850
  }
@@ -10736,7 +10899,8 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
10736
10899
  track.channelCount = config.channelCount;
10737
10900
  track.codec = config.codec;
10738
10901
  track.manifestCodec = config.manifestCodec;
10739
- logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10902
+ track.parsedCodec = config.parsedCodec;
10903
+ logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10740
10904
  }
10741
10905
  }
10742
10906
  function getFrameDuration(samplerate) {
@@ -11327,6 +11491,110 @@ class BaseVideoParser {
11327
11491
  logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
11328
11492
  }
11329
11493
  }
11494
+ parseNALu(track, array) {
11495
+ const len = array.byteLength;
11496
+ let state = track.naluState || 0;
11497
+ const lastState = state;
11498
+ const units = [];
11499
+ let i = 0;
11500
+ let value;
11501
+ let overflow;
11502
+ let unitType;
11503
+ let lastUnitStart = -1;
11504
+ let lastUnitType = 0;
11505
+ // logger.log('PES:' + Hex.hexDump(array));
11506
+
11507
+ if (state === -1) {
11508
+ // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
11509
+ lastUnitStart = 0;
11510
+ // NALu type is value read from offset 0
11511
+ lastUnitType = this.getNALuType(array, 0);
11512
+ state = 0;
11513
+ i = 1;
11514
+ }
11515
+ while (i < len) {
11516
+ value = array[i++];
11517
+ // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
11518
+ if (!state) {
11519
+ state = value ? 0 : 1;
11520
+ continue;
11521
+ }
11522
+ if (state === 1) {
11523
+ state = value ? 0 : 2;
11524
+ continue;
11525
+ }
11526
+ // here we have state either equal to 2 or 3
11527
+ if (!value) {
11528
+ state = 3;
11529
+ } else if (value === 1) {
11530
+ overflow = i - state - 1;
11531
+ if (lastUnitStart >= 0) {
11532
+ const unit = {
11533
+ data: array.subarray(lastUnitStart, overflow),
11534
+ type: lastUnitType
11535
+ };
11536
+ // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
11537
+ units.push(unit);
11538
+ } else {
11539
+ // lastUnitStart is undefined => this is the first start code found in this PES packet
11540
+ // first check if start code delimiter is overlapping between 2 PES packets,
11541
+ // ie it started in last packet (lastState not zero)
11542
+ // and ended at the beginning of this PES packet (i <= 4 - lastState)
11543
+ const lastUnit = this.getLastNalUnit(track.samples);
11544
+ if (lastUnit) {
11545
+ if (lastState && i <= 4 - lastState) {
11546
+ // start delimiter overlapping between PES packets
11547
+ // strip start delimiter bytes from the end of last NAL unit
11548
+ // check if lastUnit had a state different from zero
11549
+ if (lastUnit.state) {
11550
+ // strip last bytes
11551
+ lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
11552
+ }
11553
+ }
11554
+ // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
11555
+
11556
+ if (overflow > 0) {
11557
+ // logger.log('first NALU found with overflow:' + overflow);
11558
+ lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
11559
+ lastUnit.state = 0;
11560
+ }
11561
+ }
11562
+ }
11563
+ // check if we can read unit type
11564
+ if (i < len) {
11565
+ unitType = this.getNALuType(array, i);
11566
+ // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
11567
+ lastUnitStart = i;
11568
+ lastUnitType = unitType;
11569
+ state = 0;
11570
+ } else {
11571
+ // not enough byte to read unit type. let's read it on next PES parsing
11572
+ state = -1;
11573
+ }
11574
+ } else {
11575
+ state = 0;
11576
+ }
11577
+ }
11578
+ if (lastUnitStart >= 0 && state >= 0) {
11579
+ const unit = {
11580
+ data: array.subarray(lastUnitStart, len),
11581
+ type: lastUnitType,
11582
+ state: state
11583
+ };
11584
+ units.push(unit);
11585
+ // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
11586
+ }
11587
+ // no NALu found
11588
+ if (units.length === 0) {
11589
+ // append pes.data to previous NAL unit
11590
+ const lastUnit = this.getLastNalUnit(track.samples);
11591
+ if (lastUnit) {
11592
+ lastUnit.data = appendUint8Array(lastUnit.data, array);
11593
+ }
11594
+ }
11595
+ track.naluState = state;
11596
+ return units;
11597
+ }
11330
11598
  }
11331
11599
 
11332
11600
  /**
@@ -11469,21 +11737,171 @@ class ExpGolomb {
11469
11737
  readUInt() {
11470
11738
  return this.readBits(32);
11471
11739
  }
11740
+ }
11741
+
11742
+ class AvcVideoParser extends BaseVideoParser {
11743
+ parsePES(track, textTrack, pes, last, duration) {
11744
+ const units = this.parseNALu(track, pes.data);
11745
+ let VideoSample = this.VideoSample;
11746
+ let push;
11747
+ let spsfound = false;
11748
+ // free pes.data to save up some memory
11749
+ pes.data = null;
11750
+
11751
+ // if new NAL units found and last sample still there, let's push ...
11752
+ // this helps parsing streams with missing AUD (only do this if AUD never found)
11753
+ if (VideoSample && units.length && !track.audFound) {
11754
+ this.pushAccessUnit(VideoSample, track);
11755
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
11756
+ }
11757
+ units.forEach(unit => {
11758
+ var _VideoSample2;
11759
+ switch (unit.type) {
11760
+ // NDR
11761
+ case 1:
11762
+ {
11763
+ let iskey = false;
11764
+ push = true;
11765
+ const data = unit.data;
11766
+ // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
11767
+ if (spsfound && data.length > 4) {
11768
+ // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
11769
+ const sliceType = this.readSliceType(data);
11770
+ // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
11771
+ // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
11772
+ // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
11773
+ // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
11774
+ // if (sliceType === 2 || sliceType === 7) {
11775
+ if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
11776
+ iskey = true;
11777
+ }
11778
+ }
11779
+ if (iskey) {
11780
+ var _VideoSample;
11781
+ // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
11782
+ if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
11783
+ this.pushAccessUnit(VideoSample, track);
11784
+ VideoSample = this.VideoSample = null;
11785
+ }
11786
+ }
11787
+ if (!VideoSample) {
11788
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11789
+ }
11790
+ VideoSample.frame = true;
11791
+ VideoSample.key = iskey;
11792
+ break;
11793
+ // IDR
11794
+ }
11795
+ case 5:
11796
+ push = true;
11797
+ // handle PES not starting with AUD
11798
+ // if we have frame data already, that cannot belong to the same frame, so force a push
11799
+ if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
11800
+ this.pushAccessUnit(VideoSample, track);
11801
+ VideoSample = this.VideoSample = null;
11802
+ }
11803
+ if (!VideoSample) {
11804
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11805
+ }
11806
+ VideoSample.key = true;
11807
+ VideoSample.frame = true;
11808
+ break;
11809
+ // SEI
11810
+ case 6:
11811
+ {
11812
+ push = true;
11813
+ parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
11814
+ break;
11815
+ // SPS
11816
+ }
11817
+ case 7:
11818
+ {
11819
+ var _track$pixelRatio, _track$pixelRatio2;
11820
+ push = true;
11821
+ spsfound = true;
11822
+ const sps = unit.data;
11823
+ const config = this.readSPS(sps);
11824
+ 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]) {
11825
+ track.width = config.width;
11826
+ track.height = config.height;
11827
+ track.pixelRatio = config.pixelRatio;
11828
+ track.sps = [sps];
11829
+ track.duration = duration;
11830
+ const codecarray = sps.subarray(1, 4);
11831
+ let codecstring = 'avc1.';
11832
+ for (let i = 0; i < 3; i++) {
11833
+ let h = codecarray[i].toString(16);
11834
+ if (h.length < 2) {
11835
+ h = '0' + h;
11836
+ }
11837
+ codecstring += h;
11838
+ }
11839
+ track.codec = codecstring;
11840
+ }
11841
+ break;
11842
+ }
11843
+ // PPS
11844
+ case 8:
11845
+ push = true;
11846
+ track.pps = [unit.data];
11847
+ break;
11848
+ // AUD
11849
+ case 9:
11850
+ push = true;
11851
+ track.audFound = true;
11852
+ if (VideoSample) {
11853
+ this.pushAccessUnit(VideoSample, track);
11854
+ }
11855
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
11856
+ break;
11857
+ // Filler Data
11858
+ case 12:
11859
+ push = true;
11860
+ break;
11861
+ default:
11862
+ push = false;
11863
+ if (VideoSample) {
11864
+ VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
11865
+ }
11866
+ break;
11867
+ }
11868
+ if (VideoSample && push) {
11869
+ const units = VideoSample.units;
11870
+ units.push(unit);
11871
+ }
11872
+ });
11873
+ // if last PES packet, push samples
11874
+ if (last && VideoSample) {
11875
+ this.pushAccessUnit(VideoSample, track);
11876
+ this.VideoSample = null;
11877
+ }
11878
+ }
11879
+ getNALuType(data, offset) {
11880
+ return data[offset] & 0x1f;
11881
+ }
11882
+ readSliceType(data) {
11883
+ const eg = new ExpGolomb(data);
11884
+ // skip NALu type
11885
+ eg.readUByte();
11886
+ // discard first_mb_in_slice
11887
+ eg.readUEG();
11888
+ // return slice_type
11889
+ return eg.readUEG();
11890
+ }
11472
11891
 
11473
11892
  /**
11474
- * Advance the ExpGolomb decoder past a scaling list. The scaling
11475
- * list is optionally transmitted as part of a sequence parameter
11893
+ * The scaling list is optionally transmitted as part of a sequence parameter
11476
11894
  * set and is not relevant to transmuxing.
11477
11895
  * @param count the number of entries in this scaling list
11478
11896
  * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
11479
11897
  */
11480
- skipScalingList(count) {
11898
+ skipScalingList(count, reader) {
11481
11899
  let lastScale = 8;
11482
11900
  let nextScale = 8;
11483
11901
  let deltaScale;
11484
11902
  for (let j = 0; j < count; j++) {
11485
11903
  if (nextScale !== 0) {
11486
- deltaScale = this.readEG();
11904
+ deltaScale = reader.readEG();
11487
11905
  nextScale = (lastScale + deltaScale + 256) % 256;
11488
11906
  }
11489
11907
  lastScale = nextScale === 0 ? lastScale : nextScale;
@@ -11498,7 +11916,8 @@ class ExpGolomb {
11498
11916
  * sequence parameter set, including the dimensions of the
11499
11917
  * associated video frames.
11500
11918
  */
11501
- readSPS() {
11919
+ readSPS(sps) {
11920
+ const eg = new ExpGolomb(sps);
11502
11921
  let frameCropLeftOffset = 0;
11503
11922
  let frameCropRightOffset = 0;
11504
11923
  let frameCropTopOffset = 0;
@@ -11506,13 +11925,13 @@ class ExpGolomb {
11506
11925
  let numRefFramesInPicOrderCntCycle;
11507
11926
  let scalingListCount;
11508
11927
  let i;
11509
- const readUByte = this.readUByte.bind(this);
11510
- const readBits = this.readBits.bind(this);
11511
- const readUEG = this.readUEG.bind(this);
11512
- const readBoolean = this.readBoolean.bind(this);
11513
- const skipBits = this.skipBits.bind(this);
11514
- const skipEG = this.skipEG.bind(this);
11515
- const skipUEG = this.skipUEG.bind(this);
11928
+ const readUByte = eg.readUByte.bind(eg);
11929
+ const readBits = eg.readBits.bind(eg);
11930
+ const readUEG = eg.readUEG.bind(eg);
11931
+ const readBoolean = eg.readBoolean.bind(eg);
11932
+ const skipBits = eg.skipBits.bind(eg);
11933
+ const skipEG = eg.skipEG.bind(eg);
11934
+ const skipUEG = eg.skipUEG.bind(eg);
11516
11935
  const skipScalingList = this.skipScalingList.bind(this);
11517
11936
  readUByte();
11518
11937
  const profileIdc = readUByte(); // profile_idc
@@ -11537,9 +11956,9 @@ class ExpGolomb {
11537
11956
  if (readBoolean()) {
11538
11957
  // seq_scaling_list_present_flag[ i ]
11539
11958
  if (i < 6) {
11540
- skipScalingList(16);
11959
+ skipScalingList(16, eg);
11541
11960
  } else {
11542
- skipScalingList(64);
11961
+ skipScalingList(64, eg);
11543
11962
  }
11544
11963
  }
11545
11964
  }
@@ -11644,19 +12063,15 @@ class ExpGolomb {
11644
12063
  pixelRatio: pixelRatio
11645
12064
  };
11646
12065
  }
11647
- readSliceType() {
11648
- // skip NALu type
11649
- this.readUByte();
11650
- // discard first_mb_in_slice
11651
- this.readUEG();
11652
- // return slice_type
11653
- return this.readUEG();
11654
- }
11655
12066
  }
11656
12067
 
11657
- class AvcVideoParser extends BaseVideoParser {
11658
- parseAVCPES(track, textTrack, pes, last, duration) {
11659
- const units = this.parseAVCNALu(track, pes.data);
12068
+ class HevcVideoParser extends BaseVideoParser {
12069
+ constructor(...args) {
12070
+ super(...args);
12071
+ this.initVPS = null;
12072
+ }
12073
+ parsePES(track, textTrack, pes, last, duration) {
12074
+ const units = this.parseNALu(track, pes.data);
11660
12075
  let VideoSample = this.VideoSample;
11661
12076
  let push;
11662
12077
  let spsfound = false;
@@ -11672,42 +12087,49 @@ class AvcVideoParser extends BaseVideoParser {
11672
12087
  units.forEach(unit => {
11673
12088
  var _VideoSample2;
11674
12089
  switch (unit.type) {
11675
- // NDR
12090
+ // NON-IDR, NON RANDOM ACCESS SLICE
12091
+ case 0:
11676
12092
  case 1:
11677
- {
11678
- let iskey = false;
11679
- push = true;
11680
- const data = unit.data;
11681
- // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
11682
- if (spsfound && data.length > 4) {
11683
- // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
11684
- const sliceType = new ExpGolomb(data).readSliceType();
11685
- // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
11686
- // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
11687
- // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
11688
- // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
11689
- // if (sliceType === 2 || sliceType === 7) {
11690
- if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
11691
- iskey = true;
11692
- }
11693
- }
11694
- if (iskey) {
11695
- var _VideoSample;
11696
- // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
11697
- if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
11698
- this.pushAccessUnit(VideoSample, track);
11699
- VideoSample = this.VideoSample = null;
11700
- }
11701
- }
11702
- if (!VideoSample) {
11703
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12093
+ case 2:
12094
+ case 3:
12095
+ case 4:
12096
+ case 5:
12097
+ case 6:
12098
+ case 7:
12099
+ case 8:
12100
+ case 9:
12101
+ if (!VideoSample) {
12102
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12103
+ }
12104
+ VideoSample.frame = true;
12105
+ push = true;
12106
+ break;
12107
+
12108
+ // CRA, BLA (random access picture)
12109
+ case 16:
12110
+ case 17:
12111
+ case 18:
12112
+ case 21:
12113
+ push = true;
12114
+ if (spsfound) {
12115
+ var _VideoSample;
12116
+ // handle PES not starting with AUD
12117
+ // if we have frame data already, that cannot belong to the same frame, so force a push
12118
+ if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
12119
+ this.pushAccessUnit(VideoSample, track);
12120
+ VideoSample = this.VideoSample = null;
11704
12121
  }
11705
- VideoSample.frame = true;
11706
- VideoSample.key = iskey;
11707
- break;
11708
- // IDR
11709
12122
  }
11710
- case 5:
12123
+ if (!VideoSample) {
12124
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12125
+ }
12126
+ VideoSample.key = true;
12127
+ VideoSample.frame = true;
12128
+ break;
12129
+
12130
+ // IDR
12131
+ case 19:
12132
+ case 20:
11711
12133
  push = true;
11712
12134
  // handle PES not starting with AUD
11713
12135
  // if we have frame data already, that cannot belong to the same frame, so force a push
@@ -11721,48 +12143,76 @@ class AvcVideoParser extends BaseVideoParser {
11721
12143
  VideoSample.key = true;
11722
12144
  VideoSample.frame = true;
11723
12145
  break;
12146
+
11724
12147
  // SEI
11725
- case 6:
11726
- {
11727
- push = true;
11728
- parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
11729
- break;
11730
- // SPS
12148
+ case 39:
12149
+ push = true;
12150
+ parseSEIMessageFromNALu(unit.data, 2,
12151
+ // NALu header size
12152
+ pes.pts, textTrack.samples);
12153
+ break;
12154
+
12155
+ // VPS
12156
+ case 32:
12157
+ push = true;
12158
+ if (!track.vps) {
12159
+ const config = this.readVPS(unit.data);
12160
+ track.params = _objectSpread2({}, config);
12161
+ this.initVPS = unit.data;
11731
12162
  }
11732
- case 7:
11733
- {
11734
- var _track$pixelRatio, _track$pixelRatio2;
11735
- push = true;
11736
- spsfound = true;
11737
- const sps = unit.data;
11738
- const expGolombDecoder = new ExpGolomb(sps);
11739
- const config = expGolombDecoder.readSPS();
11740
- 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]) {
12163
+ track.vps = [unit.data];
12164
+ break;
12165
+
12166
+ // SPS
12167
+ case 33:
12168
+ push = true;
12169
+ spsfound = true;
12170
+ if (typeof track.params === 'object') {
12171
+ if (track.vps !== undefined && track.vps[0] !== this.initVPS && track.sps !== undefined && !this.matchSPS(track.sps[0], unit.data)) {
12172
+ this.initVPS = track.vps[0];
12173
+ track.sps = track.pps = undefined;
12174
+ }
12175
+ if (!track.sps) {
12176
+ const config = this.readSPS(unit.data);
11741
12177
  track.width = config.width;
11742
12178
  track.height = config.height;
11743
12179
  track.pixelRatio = config.pixelRatio;
11744
- track.sps = [sps];
11745
12180
  track.duration = duration;
11746
- const codecarray = sps.subarray(1, 4);
11747
- let codecstring = 'avc1.';
11748
- for (let i = 0; i < 3; i++) {
11749
- let h = codecarray[i].toString(16);
11750
- if (h.length < 2) {
11751
- h = '0' + h;
11752
- }
11753
- codecstring += h;
12181
+ track.codec = config.codecString;
12182
+ track.sps = [];
12183
+ for (const prop in config.params) {
12184
+ track.params[prop] = config.params[prop];
11754
12185
  }
11755
- track.codec = codecstring;
11756
12186
  }
11757
- break;
12187
+ if (track.vps !== undefined && track.vps[0] === this.initVPS) {
12188
+ track.sps.push(unit.data);
12189
+ }
11758
12190
  }
12191
+ if (!VideoSample) {
12192
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12193
+ }
12194
+ VideoSample.key = true;
12195
+ break;
12196
+
11759
12197
  // PPS
11760
- case 8:
12198
+ case 34:
11761
12199
  push = true;
11762
- track.pps = [unit.data];
12200
+ if (typeof track.params === 'object') {
12201
+ if (!track.pps) {
12202
+ track.pps = [];
12203
+ const config = this.readPPS(unit.data);
12204
+ for (const prop in config) {
12205
+ track.params[prop] = config[prop];
12206
+ }
12207
+ }
12208
+ if (this.initVPS !== null || track.pps.length === 0) {
12209
+ track.pps.push(unit.data);
12210
+ }
12211
+ }
11763
12212
  break;
11764
- // AUD
11765
- case 9:
12213
+
12214
+ // ACCESS UNIT DELIMITER
12215
+ case 35:
11766
12216
  push = true;
11767
12217
  track.audFound = true;
11768
12218
  if (VideoSample) {
@@ -11770,14 +12220,10 @@ class AvcVideoParser extends BaseVideoParser {
11770
12220
  }
11771
12221
  VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
11772
12222
  break;
11773
- // Filler Data
11774
- case 12:
11775
- push = true;
11776
- break;
11777
12223
  default:
11778
12224
  push = false;
11779
12225
  if (VideoSample) {
11780
- VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
12226
+ VideoSample.debug += 'unknown or irrelevant NAL ' + unit.type + ' ';
11781
12227
  }
11782
12228
  break;
11783
12229
  }
@@ -11792,109 +12238,423 @@ class AvcVideoParser extends BaseVideoParser {
11792
12238
  this.VideoSample = null;
11793
12239
  }
11794
12240
  }
11795
- parseAVCNALu(track, array) {
11796
- const len = array.byteLength;
11797
- let state = track.naluState || 0;
11798
- const lastState = state;
11799
- const units = [];
11800
- let i = 0;
11801
- let value;
11802
- let overflow;
11803
- let unitType;
11804
- let lastUnitStart = -1;
11805
- let lastUnitType = 0;
11806
- // logger.log('PES:' + Hex.hexDump(array));
11807
-
11808
- if (state === -1) {
11809
- // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
11810
- lastUnitStart = 0;
11811
- // NALu type is value read from offset 0
11812
- lastUnitType = array[0] & 0x1f;
11813
- state = 0;
11814
- i = 1;
11815
- }
11816
- while (i < len) {
11817
- value = array[i++];
11818
- // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
11819
- if (!state) {
11820
- state = value ? 0 : 1;
11821
- continue;
11822
- }
11823
- if (state === 1) {
11824
- state = value ? 0 : 2;
11825
- continue;
12241
+ getNALuType(data, offset) {
12242
+ return (data[offset] & 0x7e) >>> 1;
12243
+ }
12244
+ ebsp2rbsp(arr) {
12245
+ const dst = new Uint8Array(arr.byteLength);
12246
+ let dstIdx = 0;
12247
+ for (let i = 0; i < arr.byteLength; i++) {
12248
+ if (i >= 2) {
12249
+ // Unescape: Skip 0x03 after 00 00
12250
+ if (arr[i] === 0x03 && arr[i - 1] === 0x00 && arr[i - 2] === 0x00) {
12251
+ continue;
12252
+ }
11826
12253
  }
11827
- // here we have state either equal to 2 or 3
11828
- if (!value) {
11829
- state = 3;
11830
- } else if (value === 1) {
11831
- overflow = i - state - 1;
11832
- if (lastUnitStart >= 0) {
11833
- const unit = {
11834
- data: array.subarray(lastUnitStart, overflow),
11835
- type: lastUnitType
11836
- };
11837
- // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
11838
- units.push(unit);
11839
- } else {
11840
- // lastUnitStart is undefined => this is the first start code found in this PES packet
11841
- // first check if start code delimiter is overlapping between 2 PES packets,
11842
- // ie it started in last packet (lastState not zero)
11843
- // and ended at the beginning of this PES packet (i <= 4 - lastState)
11844
- const lastUnit = this.getLastNalUnit(track.samples);
11845
- if (lastUnit) {
11846
- if (lastState && i <= 4 - lastState) {
11847
- // start delimiter overlapping between PES packets
11848
- // strip start delimiter bytes from the end of last NAL unit
11849
- // check if lastUnit had a state different from zero
11850
- if (lastUnit.state) {
11851
- // strip last bytes
11852
- lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
11853
- }
11854
- }
11855
- // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
12254
+ dst[dstIdx] = arr[i];
12255
+ dstIdx++;
12256
+ }
12257
+ return new Uint8Array(dst.buffer, 0, dstIdx);
12258
+ }
12259
+ readVPS(vps) {
12260
+ const eg = new ExpGolomb(vps);
12261
+ // remove header
12262
+ eg.readUByte();
12263
+ eg.readUByte();
12264
+ eg.readBits(4); // video_parameter_set_id
12265
+ eg.skipBits(2);
12266
+ eg.readBits(6); // max_layers_minus1
12267
+ const max_sub_layers_minus1 = eg.readBits(3);
12268
+ const temporal_id_nesting_flag = eg.readBoolean();
12269
+ // ...vui fps can be here, but empty fps value is not critical for metadata
11856
12270
 
11857
- if (overflow > 0) {
11858
- // logger.log('first NALU found with overflow:' + overflow);
11859
- lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
11860
- lastUnit.state = 0;
12271
+ return {
12272
+ numTemporalLayers: max_sub_layers_minus1 + 1,
12273
+ temporalIdNested: temporal_id_nesting_flag
12274
+ };
12275
+ }
12276
+ readSPS(sps) {
12277
+ const eg = new ExpGolomb(this.ebsp2rbsp(sps));
12278
+ eg.readUByte();
12279
+ eg.readUByte();
12280
+ eg.readBits(4); //video_parameter_set_id
12281
+ const max_sub_layers_minus1 = eg.readBits(3);
12282
+ eg.readBoolean(); // temporal_id_nesting_flag
12283
+
12284
+ // profile_tier_level
12285
+ const general_profile_space = eg.readBits(2);
12286
+ const general_tier_flag = eg.readBoolean();
12287
+ const general_profile_idc = eg.readBits(5);
12288
+ const general_profile_compatibility_flags_1 = eg.readUByte();
12289
+ const general_profile_compatibility_flags_2 = eg.readUByte();
12290
+ const general_profile_compatibility_flags_3 = eg.readUByte();
12291
+ const general_profile_compatibility_flags_4 = eg.readUByte();
12292
+ const general_constraint_indicator_flags_1 = eg.readUByte();
12293
+ const general_constraint_indicator_flags_2 = eg.readUByte();
12294
+ const general_constraint_indicator_flags_3 = eg.readUByte();
12295
+ const general_constraint_indicator_flags_4 = eg.readUByte();
12296
+ const general_constraint_indicator_flags_5 = eg.readUByte();
12297
+ const general_constraint_indicator_flags_6 = eg.readUByte();
12298
+ const general_level_idc = eg.readUByte();
12299
+ const sub_layer_profile_present_flags = [];
12300
+ const sub_layer_level_present_flags = [];
12301
+ for (let i = 0; i < max_sub_layers_minus1; i++) {
12302
+ sub_layer_profile_present_flags.push(eg.readBoolean());
12303
+ sub_layer_level_present_flags.push(eg.readBoolean());
12304
+ }
12305
+ if (max_sub_layers_minus1 > 0) {
12306
+ for (let i = max_sub_layers_minus1; i < 8; i++) {
12307
+ eg.readBits(2);
12308
+ }
12309
+ }
12310
+ for (let i = 0; i < max_sub_layers_minus1; i++) {
12311
+ if (sub_layer_profile_present_flags[i]) {
12312
+ eg.readUByte(); // sub_layer_profile_space, sub_layer_tier_flag, sub_layer_profile_idc
12313
+ eg.readUByte();
12314
+ eg.readUByte();
12315
+ eg.readUByte();
12316
+ eg.readUByte(); // sub_layer_profile_compatibility_flag
12317
+ eg.readUByte();
12318
+ eg.readUByte();
12319
+ eg.readUByte();
12320
+ eg.readUByte();
12321
+ eg.readUByte();
12322
+ eg.readUByte();
12323
+ }
12324
+ if (sub_layer_level_present_flags[i]) {
12325
+ eg.readUByte();
12326
+ }
12327
+ }
12328
+ eg.readUEG(); // seq_parameter_set_id
12329
+ const chroma_format_idc = eg.readUEG();
12330
+ if (chroma_format_idc == 3) {
12331
+ eg.skipBits(1); //separate_colour_plane_flag
12332
+ }
12333
+ const pic_width_in_luma_samples = eg.readUEG();
12334
+ const pic_height_in_luma_samples = eg.readUEG();
12335
+ const conformance_window_flag = eg.readBoolean();
12336
+ let pic_left_offset = 0,
12337
+ pic_right_offset = 0,
12338
+ pic_top_offset = 0,
12339
+ pic_bottom_offset = 0;
12340
+ if (conformance_window_flag) {
12341
+ pic_left_offset += eg.readUEG();
12342
+ pic_right_offset += eg.readUEG();
12343
+ pic_top_offset += eg.readUEG();
12344
+ pic_bottom_offset += eg.readUEG();
12345
+ }
12346
+ const bit_depth_luma_minus8 = eg.readUEG();
12347
+ const bit_depth_chroma_minus8 = eg.readUEG();
12348
+ const log2_max_pic_order_cnt_lsb_minus4 = eg.readUEG();
12349
+ const sub_layer_ordering_info_present_flag = eg.readBoolean();
12350
+ for (let i = sub_layer_ordering_info_present_flag ? 0 : max_sub_layers_minus1; i <= max_sub_layers_minus1; i++) {
12351
+ eg.skipUEG(); // max_dec_pic_buffering_minus1[i]
12352
+ eg.skipUEG(); // max_num_reorder_pics[i]
12353
+ eg.skipUEG(); // max_latency_increase_plus1[i]
12354
+ }
12355
+ eg.skipUEG(); // log2_min_luma_coding_block_size_minus3
12356
+ eg.skipUEG(); // log2_diff_max_min_luma_coding_block_size
12357
+ eg.skipUEG(); // log2_min_transform_block_size_minus2
12358
+ eg.skipUEG(); // log2_diff_max_min_transform_block_size
12359
+ eg.skipUEG(); // max_transform_hierarchy_depth_inter
12360
+ eg.skipUEG(); // max_transform_hierarchy_depth_intra
12361
+ const scaling_list_enabled_flag = eg.readBoolean();
12362
+ if (scaling_list_enabled_flag) {
12363
+ const sps_scaling_list_data_present_flag = eg.readBoolean();
12364
+ if (sps_scaling_list_data_present_flag) {
12365
+ for (let sizeId = 0; sizeId < 4; sizeId++) {
12366
+ for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
12367
+ const scaling_list_pred_mode_flag = eg.readBoolean();
12368
+ if (!scaling_list_pred_mode_flag) {
12369
+ eg.readUEG(); // scaling_list_pred_matrix_id_delta
12370
+ } else {
12371
+ const coefNum = Math.min(64, 1 << 4 + (sizeId << 1));
12372
+ if (sizeId > 1) {
12373
+ eg.readEG();
12374
+ }
12375
+ for (let i = 0; i < coefNum; i++) {
12376
+ eg.readEG();
12377
+ }
11861
12378
  }
11862
12379
  }
11863
12380
  }
11864
- // check if we can read unit type
11865
- if (i < len) {
11866
- unitType = array[i] & 0x1f;
11867
- // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
11868
- lastUnitStart = i;
11869
- lastUnitType = unitType;
11870
- state = 0;
11871
- } else {
11872
- // not enough byte to read unit type. let's read it on next PES parsing
11873
- state = -1;
11874
- }
11875
- } else {
11876
- state = 0;
11877
12381
  }
11878
12382
  }
11879
- if (lastUnitStart >= 0 && state >= 0) {
11880
- const unit = {
11881
- data: array.subarray(lastUnitStart, len),
11882
- type: lastUnitType,
11883
- state: state
11884
- };
11885
- units.push(unit);
11886
- // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
11887
- }
11888
- // no NALu found
11889
- if (units.length === 0) {
11890
- // append pes.data to previous NAL unit
11891
- const lastUnit = this.getLastNalUnit(track.samples);
11892
- if (lastUnit) {
11893
- lastUnit.data = appendUint8Array(lastUnit.data, array);
12383
+ eg.readBoolean(); // amp_enabled_flag
12384
+ eg.readBoolean(); // sample_adaptive_offset_enabled_flag
12385
+ const pcm_enabled_flag = eg.readBoolean();
12386
+ if (pcm_enabled_flag) {
12387
+ eg.readUByte();
12388
+ eg.skipUEG();
12389
+ eg.skipUEG();
12390
+ eg.readBoolean();
12391
+ }
12392
+ const num_short_term_ref_pic_sets = eg.readUEG();
12393
+ let num_delta_pocs = 0;
12394
+ for (let i = 0; i < num_short_term_ref_pic_sets; i++) {
12395
+ let inter_ref_pic_set_prediction_flag = false;
12396
+ if (i !== 0) {
12397
+ inter_ref_pic_set_prediction_flag = eg.readBoolean();
12398
+ }
12399
+ if (inter_ref_pic_set_prediction_flag) {
12400
+ if (i === num_short_term_ref_pic_sets) {
12401
+ eg.readUEG();
12402
+ }
12403
+ eg.readBoolean();
12404
+ eg.readUEG();
12405
+ let next_num_delta_pocs = 0;
12406
+ for (let j = 0; j <= num_delta_pocs; j++) {
12407
+ const used_by_curr_pic_flag = eg.readBoolean();
12408
+ let use_delta_flag = false;
12409
+ if (!used_by_curr_pic_flag) {
12410
+ use_delta_flag = eg.readBoolean();
12411
+ }
12412
+ if (used_by_curr_pic_flag || use_delta_flag) {
12413
+ next_num_delta_pocs++;
12414
+ }
12415
+ }
12416
+ num_delta_pocs = next_num_delta_pocs;
12417
+ } else {
12418
+ const num_negative_pics = eg.readUEG();
12419
+ const num_positive_pics = eg.readUEG();
12420
+ num_delta_pocs = num_negative_pics + num_positive_pics;
12421
+ for (let j = 0; j < num_negative_pics; j++) {
12422
+ eg.readUEG();
12423
+ eg.readBoolean();
12424
+ }
12425
+ for (let j = 0; j < num_positive_pics; j++) {
12426
+ eg.readUEG();
12427
+ eg.readBoolean();
12428
+ }
12429
+ }
12430
+ }
12431
+ const long_term_ref_pics_present_flag = eg.readBoolean();
12432
+ if (long_term_ref_pics_present_flag) {
12433
+ const num_long_term_ref_pics_sps = eg.readUEG();
12434
+ for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
12435
+ for (let j = 0; j < log2_max_pic_order_cnt_lsb_minus4 + 4; j++) {
12436
+ eg.readBits(1);
12437
+ }
12438
+ eg.readBits(1);
12439
+ }
12440
+ }
12441
+ let min_spatial_segmentation_idc = 0;
12442
+ let sar_width = 1,
12443
+ sar_height = 1;
12444
+ let fps_fixed = true,
12445
+ fps_den = 1,
12446
+ fps_num = 0;
12447
+ eg.readBoolean(); // sps_temporal_mvp_enabled_flag
12448
+ eg.readBoolean(); // strong_intra_smoothing_enabled_flag
12449
+ let default_display_window_flag = false;
12450
+ const vui_parameters_present_flag = eg.readBoolean();
12451
+ if (vui_parameters_present_flag) {
12452
+ const aspect_ratio_info_present_flag = eg.readBoolean();
12453
+ if (aspect_ratio_info_present_flag) {
12454
+ const aspect_ratio_idc = eg.readUByte();
12455
+ const sar_width_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];
12456
+ const sar_height_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1];
12457
+ if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) {
12458
+ sar_width = sar_width_table[aspect_ratio_idc - 1];
12459
+ sar_height = sar_height_table[aspect_ratio_idc - 1];
12460
+ } else if (aspect_ratio_idc === 255) {
12461
+ sar_width = eg.readBits(16);
12462
+ sar_height = eg.readBits(16);
12463
+ }
12464
+ }
12465
+ const overscan_info_present_flag = eg.readBoolean();
12466
+ if (overscan_info_present_flag) {
12467
+ eg.readBoolean();
12468
+ }
12469
+ const video_signal_type_present_flag = eg.readBoolean();
12470
+ if (video_signal_type_present_flag) {
12471
+ eg.readBits(3);
12472
+ eg.readBoolean();
12473
+ const colour_description_present_flag = eg.readBoolean();
12474
+ if (colour_description_present_flag) {
12475
+ eg.readUByte();
12476
+ eg.readUByte();
12477
+ eg.readUByte();
12478
+ }
12479
+ }
12480
+ const chroma_loc_info_present_flag = eg.readBoolean();
12481
+ if (chroma_loc_info_present_flag) {
12482
+ eg.readUEG();
12483
+ eg.readUEG();
12484
+ }
12485
+ eg.readBoolean(); // neutral_chroma_indication_flag
12486
+ eg.readBoolean(); // field_seq_flag
12487
+ eg.readBoolean(); // frame_field_info_present_flag
12488
+ default_display_window_flag = eg.readBoolean();
12489
+ if (default_display_window_flag) {
12490
+ pic_left_offset += eg.readUEG();
12491
+ pic_right_offset += eg.readUEG();
12492
+ pic_top_offset += eg.readUEG();
12493
+ pic_bottom_offset += eg.readUEG();
12494
+ }
12495
+ const vui_timing_info_present_flag = eg.readBoolean();
12496
+ if (vui_timing_info_present_flag) {
12497
+ fps_den = eg.readBits(32);
12498
+ fps_num = eg.readBits(32);
12499
+ const vui_poc_proportional_to_timing_flag = eg.readBoolean();
12500
+ if (vui_poc_proportional_to_timing_flag) {
12501
+ eg.readUEG();
12502
+ }
12503
+ const vui_hrd_parameters_present_flag = eg.readBoolean();
12504
+ if (vui_hrd_parameters_present_flag) {
12505
+ //const commonInfPresentFlag = true;
12506
+ //if (commonInfPresentFlag) {
12507
+ const nal_hrd_parameters_present_flag = eg.readBoolean();
12508
+ const vcl_hrd_parameters_present_flag = eg.readBoolean();
12509
+ let sub_pic_hrd_params_present_flag = false;
12510
+ if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
12511
+ sub_pic_hrd_params_present_flag = eg.readBoolean();
12512
+ if (sub_pic_hrd_params_present_flag) {
12513
+ eg.readUByte();
12514
+ eg.readBits(5);
12515
+ eg.readBoolean();
12516
+ eg.readBits(5);
12517
+ }
12518
+ eg.readBits(4); // bit_rate_scale
12519
+ eg.readBits(4); // cpb_size_scale
12520
+ if (sub_pic_hrd_params_present_flag) {
12521
+ eg.readBits(4);
12522
+ }
12523
+ eg.readBits(5);
12524
+ eg.readBits(5);
12525
+ eg.readBits(5);
12526
+ }
12527
+ //}
12528
+ for (let i = 0; i <= max_sub_layers_minus1; i++) {
12529
+ fps_fixed = eg.readBoolean(); // fixed_pic_rate_general_flag
12530
+ const fixed_pic_rate_within_cvs_flag = fps_fixed || eg.readBoolean();
12531
+ let low_delay_hrd_flag = false;
12532
+ if (fixed_pic_rate_within_cvs_flag) {
12533
+ eg.readEG();
12534
+ } else {
12535
+ low_delay_hrd_flag = eg.readBoolean();
12536
+ }
12537
+ const cpb_cnt = low_delay_hrd_flag ? 1 : eg.readUEG() + 1;
12538
+ if (nal_hrd_parameters_present_flag) {
12539
+ for (let j = 0; j < cpb_cnt; j++) {
12540
+ eg.readUEG();
12541
+ eg.readUEG();
12542
+ if (sub_pic_hrd_params_present_flag) {
12543
+ eg.readUEG();
12544
+ eg.readUEG();
12545
+ }
12546
+ eg.skipBits(1);
12547
+ }
12548
+ }
12549
+ if (vcl_hrd_parameters_present_flag) {
12550
+ for (let j = 0; j < cpb_cnt; j++) {
12551
+ eg.readUEG();
12552
+ eg.readUEG();
12553
+ if (sub_pic_hrd_params_present_flag) {
12554
+ eg.readUEG();
12555
+ eg.readUEG();
12556
+ }
12557
+ eg.skipBits(1);
12558
+ }
12559
+ }
12560
+ }
12561
+ }
11894
12562
  }
12563
+ const bitstream_restriction_flag = eg.readBoolean();
12564
+ if (bitstream_restriction_flag) {
12565
+ eg.readBoolean(); // tiles_fixed_structure_flag
12566
+ eg.readBoolean(); // motion_vectors_over_pic_boundaries_flag
12567
+ eg.readBoolean(); // restricted_ref_pic_lists_flag
12568
+ min_spatial_segmentation_idc = eg.readUEG();
12569
+ }
12570
+ }
12571
+ let width = pic_width_in_luma_samples,
12572
+ height = pic_height_in_luma_samples;
12573
+ if (conformance_window_flag || default_display_window_flag) {
12574
+ let chroma_scale_w = 1,
12575
+ chroma_scale_h = 1;
12576
+ if (chroma_format_idc === 1) {
12577
+ // YUV 420
12578
+ chroma_scale_w = chroma_scale_h = 2;
12579
+ } else if (chroma_format_idc == 2) {
12580
+ // YUV 422
12581
+ chroma_scale_w = 2;
12582
+ }
12583
+ width = pic_width_in_luma_samples - chroma_scale_w * pic_right_offset - chroma_scale_w * pic_left_offset;
12584
+ height = pic_height_in_luma_samples - chroma_scale_h * pic_bottom_offset - chroma_scale_h * pic_top_offset;
12585
+ }
12586
+ const profile_space_string = general_profile_space ? ['A', 'B', 'C'][general_profile_space] : '';
12587
+ const profile_compatibility_buf = general_profile_compatibility_flags_1 << 24 | general_profile_compatibility_flags_2 << 16 | general_profile_compatibility_flags_3 << 8 | general_profile_compatibility_flags_4;
12588
+ let profile_compatibility_rev = 0;
12589
+ for (let i = 0; i < 32; i++) {
12590
+ profile_compatibility_rev = (profile_compatibility_rev | (profile_compatibility_buf >> i & 1) << 31 - i) >>> 0; // reverse bit position (and cast as UInt32)
12591
+ }
12592
+ let profile_compatibility_flags_string = profile_compatibility_rev.toString(16);
12593
+ if (general_profile_idc === 1 && profile_compatibility_flags_string === '2') {
12594
+ profile_compatibility_flags_string = '6';
12595
+ }
12596
+ const tier_flag_string = general_tier_flag ? 'H' : 'L';
12597
+ return {
12598
+ codecString: `hvc1.${profile_space_string}${general_profile_idc}.${profile_compatibility_flags_string}.${tier_flag_string}${general_level_idc}.B0`,
12599
+ params: {
12600
+ general_tier_flag,
12601
+ general_profile_idc,
12602
+ general_profile_space,
12603
+ general_profile_compatibility_flags: [general_profile_compatibility_flags_1, general_profile_compatibility_flags_2, general_profile_compatibility_flags_3, general_profile_compatibility_flags_4],
12604
+ general_constraint_indicator_flags: [general_constraint_indicator_flags_1, general_constraint_indicator_flags_2, general_constraint_indicator_flags_3, general_constraint_indicator_flags_4, general_constraint_indicator_flags_5, general_constraint_indicator_flags_6],
12605
+ general_level_idc,
12606
+ bit_depth: bit_depth_luma_minus8 + 8,
12607
+ bit_depth_luma_minus8,
12608
+ bit_depth_chroma_minus8,
12609
+ min_spatial_segmentation_idc,
12610
+ chroma_format_idc: chroma_format_idc,
12611
+ frame_rate: {
12612
+ fixed: fps_fixed,
12613
+ fps: fps_num / fps_den
12614
+ }
12615
+ },
12616
+ width,
12617
+ height,
12618
+ pixelRatio: [sar_width, sar_height]
12619
+ };
12620
+ }
12621
+ readPPS(pps) {
12622
+ const eg = new ExpGolomb(this.ebsp2rbsp(pps));
12623
+ eg.readUByte();
12624
+ eg.readUByte();
12625
+ eg.skipUEG(); // pic_parameter_set_id
12626
+ eg.skipUEG(); // seq_parameter_set_id
12627
+ eg.skipBits(2); // dependent_slice_segments_enabled_flag, output_flag_present_flag
12628
+ eg.skipBits(3); // num_extra_slice_header_bits
12629
+ eg.skipBits(2); // sign_data_hiding_enabled_flag, cabac_init_present_flag
12630
+ eg.skipUEG();
12631
+ eg.skipUEG();
12632
+ eg.skipEG(); // init_qp_minus26
12633
+ eg.skipBits(2); // constrained_intra_pred_flag, transform_skip_enabled_flag
12634
+ const cu_qp_delta_enabled_flag = eg.readBoolean();
12635
+ if (cu_qp_delta_enabled_flag) {
12636
+ eg.skipUEG();
12637
+ }
12638
+ eg.skipEG(); // cb_qp_offset
12639
+ eg.skipEG(); // cr_qp_offset
12640
+ eg.skipBits(4); // pps_slice_chroma_qp_offsets_present_flag, weighted_pred_flag, weighted_bipred_flag, transquant_bypass_enabled_flag
12641
+ const tiles_enabled_flag = eg.readBoolean();
12642
+ const entropy_coding_sync_enabled_flag = eg.readBoolean();
12643
+ let parallelismType = 1; // slice-based parallel decoding
12644
+ if (entropy_coding_sync_enabled_flag && tiles_enabled_flag) {
12645
+ parallelismType = 0; // mixed-type parallel decoding
12646
+ } else if (entropy_coding_sync_enabled_flag) {
12647
+ parallelismType = 3; // wavefront-based parallel decoding
12648
+ } else if (tiles_enabled_flag) {
12649
+ parallelismType = 2; // tile-based parallel decoding
11895
12650
  }
11896
- track.naluState = state;
11897
- return units;
12651
+ return {
12652
+ parallelismType
12653
+ };
12654
+ }
12655
+ matchSPS(sps1, sps2) {
12656
+ // compare without headers and VPS related params
12657
+ return String.fromCharCode.apply(null, sps1).substr(3) === String.fromCharCode.apply(null, sps2).substr(3);
11898
12658
  }
11899
12659
  }
11900
12660
 
@@ -11912,7 +12672,7 @@ class SampleAesDecrypter {
11912
12672
  });
11913
12673
  }
11914
12674
  decryptBuffer(encryptedData) {
11915
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
12675
+ return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
11916
12676
  }
11917
12677
 
11918
12678
  // AAC - encrypt all full 16 bytes blocks starting from offset 16
@@ -12026,7 +12786,7 @@ class TSDemuxer {
12026
12786
  this.observer = observer;
12027
12787
  this.config = config;
12028
12788
  this.typeSupported = typeSupported;
12029
- this.videoParser = new AvcVideoParser();
12789
+ this.videoParser = null;
12030
12790
  }
12031
12791
  static probe(data) {
12032
12792
  const syncOffset = TSDemuxer.syncOffset(data);
@@ -12191,7 +12951,21 @@ class TSDemuxer {
12191
12951
  case videoPid:
12192
12952
  if (stt) {
12193
12953
  if (videoData && (pes = parsePES(videoData))) {
12194
- this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
12954
+ if (this.videoParser === null) {
12955
+ switch (videoTrack.segmentCodec) {
12956
+ case 'avc':
12957
+ this.videoParser = new AvcVideoParser();
12958
+ break;
12959
+ case 'hevc':
12960
+ {
12961
+ this.videoParser = new HevcVideoParser();
12962
+ }
12963
+ break;
12964
+ }
12965
+ }
12966
+ if (this.videoParser !== null) {
12967
+ this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
12968
+ }
12195
12969
  }
12196
12970
  videoData = {
12197
12971
  data: [],
@@ -12358,8 +13132,22 @@ class TSDemuxer {
12358
13132
  // try to parse last PES packets
12359
13133
  let pes;
12360
13134
  if (videoData && (pes = parsePES(videoData))) {
12361
- this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
12362
- videoTrack.pesData = null;
13135
+ if (this.videoParser === null) {
13136
+ switch (videoTrack.segmentCodec) {
13137
+ case 'avc':
13138
+ this.videoParser = new AvcVideoParser();
13139
+ break;
13140
+ case 'hevc':
13141
+ {
13142
+ this.videoParser = new HevcVideoParser();
13143
+ }
13144
+ break;
13145
+ }
13146
+ }
13147
+ if (this.videoParser !== null) {
13148
+ this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
13149
+ videoTrack.pesData = null;
13150
+ }
12363
13151
  } else {
12364
13152
  // either avcData null or PES truncated, keep it for next frag parsing
12365
13153
  videoTrack.pesData = videoData;
@@ -12692,7 +13480,14 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
12692
13480
  logger.warn('Unsupported EC-3 in M2TS found');
12693
13481
  break;
12694
13482
  case 0x24:
12695
- logger.warn('Unsupported HEVC in M2TS found');
13483
+ // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
13484
+ {
13485
+ if (result.videoPid === -1) {
13486
+ result.videoPid = pid;
13487
+ result.segmentVideoCodec = 'hevc';
13488
+ logger.log('HEVC in M2TS found');
13489
+ }
13490
+ }
12696
13491
  break;
12697
13492
  }
12698
13493
  // move to the next table entry
@@ -12915,6 +13710,8 @@ class MP4 {
12915
13710
  avc1: [],
12916
13711
  // codingname
12917
13712
  avcC: [],
13713
+ hvc1: [],
13714
+ hvcC: [],
12918
13715
  btrt: [],
12919
13716
  dinf: [],
12920
13717
  dref: [],
@@ -13339,8 +14136,10 @@ class MP4 {
13339
14136
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
13340
14137
  }
13341
14138
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
13342
- } else {
14139
+ } else if (track.segmentCodec === 'avc') {
13343
14140
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
14141
+ } else {
14142
+ return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
13344
14143
  }
13345
14144
  }
13346
14145
  static tkhd(track) {
@@ -13478,6 +14277,84 @@ class MP4 {
13478
14277
  const result = appendUint8Array(MP4.FTYP, movie);
13479
14278
  return result;
13480
14279
  }
14280
+ static hvc1(track) {
14281
+ const ps = track.params;
14282
+ const units = [track.vps, track.sps, track.pps];
14283
+ const NALuLengthSize = 4;
14284
+ 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]);
14285
+
14286
+ // compute hvcC size in bytes
14287
+ let length = config.length;
14288
+ for (let i = 0; i < units.length; i += 1) {
14289
+ length += 3;
14290
+ for (let j = 0; j < units[i].length; j += 1) {
14291
+ length += 2 + units[i][j].length;
14292
+ }
14293
+ }
14294
+ const hvcC = new Uint8Array(length);
14295
+ hvcC.set(config, 0);
14296
+ length = config.length;
14297
+ // append parameter set units: one vps, one or more sps and pps
14298
+ const iMax = units.length - 1;
14299
+ for (let i = 0; i < units.length; i += 1) {
14300
+ hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
14301
+ length += 3;
14302
+ for (let j = 0; j < units[i].length; j += 1) {
14303
+ hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
14304
+ length += 2;
14305
+ hvcC.set(units[i][j], length);
14306
+ length += units[i][j].length;
14307
+ }
14308
+ }
14309
+ const hvcc = MP4.box(MP4.types.hvcC, hvcC);
14310
+ const width = track.width;
14311
+ const height = track.height;
14312
+ const hSpacing = track.pixelRatio[0];
14313
+ const vSpacing = track.pixelRatio[1];
14314
+ return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
14315
+ // reserved
14316
+ 0x00, 0x00, 0x00,
14317
+ // reserved
14318
+ 0x00, 0x01,
14319
+ // data_reference_index
14320
+ 0x00, 0x00,
14321
+ // pre_defined
14322
+ 0x00, 0x00,
14323
+ // reserved
14324
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14325
+ // pre_defined
14326
+ width >> 8 & 0xff, width & 0xff,
14327
+ // width
14328
+ height >> 8 & 0xff, height & 0xff,
14329
+ // height
14330
+ 0x00, 0x48, 0x00, 0x00,
14331
+ // horizresolution
14332
+ 0x00, 0x48, 0x00, 0x00,
14333
+ // vertresolution
14334
+ 0x00, 0x00, 0x00, 0x00,
14335
+ // reserved
14336
+ 0x00, 0x01,
14337
+ // frame_count
14338
+ 0x12, 0x64, 0x61, 0x69, 0x6c,
14339
+ // dailymotion/hls.js
14340
+ 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,
14341
+ // compressorname
14342
+ 0x00, 0x18,
14343
+ // depth = 24
14344
+ 0x11, 0x11]),
14345
+ // pre_defined = -1
14346
+ hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
14347
+ // bufferSizeDB
14348
+ 0x00, 0x2d, 0xc6, 0xc0,
14349
+ // maxBitrate
14350
+ 0x00, 0x2d, 0xc6, 0xc0])),
14351
+ // avgBitrate
14352
+ MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
14353
+ // hSpacing
14354
+ hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
14355
+ // vSpacing
14356
+ vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
14357
+ }
13481
14358
  }
13482
14359
  MP4.types = void 0;
13483
14360
  MP4.HDLR_TYPES = void 0;
@@ -13859,9 +14736,9 @@ class MP4Remuxer {
13859
14736
  const foundOverlap = delta < -1;
13860
14737
  if (foundHole || foundOverlap) {
13861
14738
  if (foundHole) {
13862
- logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
14739
+ logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
13863
14740
  } else {
13864
- logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
14741
+ logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
13865
14742
  }
13866
14743
  if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
13867
14744
  firstDTS = nextAvcDts;
@@ -13870,12 +14747,24 @@ class MP4Remuxer {
13870
14747
  inputSamples[0].dts = firstDTS;
13871
14748
  inputSamples[0].pts = firstPTS;
13872
14749
  } else {
14750
+ let isPTSOrderRetained = true;
13873
14751
  for (let i = 0; i < inputSamples.length; i++) {
13874
- if (inputSamples[i].dts > firstPTS) {
14752
+ if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
13875
14753
  break;
13876
14754
  }
14755
+ const prevPTS = inputSamples[i].pts;
13877
14756
  inputSamples[i].dts -= delta;
13878
14757
  inputSamples[i].pts -= delta;
14758
+
14759
+ // check to see if this sample's PTS order has changed
14760
+ // relative to the next one
14761
+ if (i < inputSamples.length - 1) {
14762
+ const nextSamplePTS = inputSamples[i + 1].pts;
14763
+ const currentSamplePTS = inputSamples[i].pts;
14764
+ const currentOrder = nextSamplePTS <= currentSamplePTS;
14765
+ const prevOrder = nextSamplePTS <= prevPTS;
14766
+ isPTSOrderRetained = currentOrder == prevOrder;
14767
+ }
13879
14768
  }
13880
14769
  }
13881
14770
  logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
@@ -14023,7 +14912,7 @@ class MP4Remuxer {
14023
14912
  }
14024
14913
  }
14025
14914
  }
14026
- // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14915
+ // next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14027
14916
  mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
14028
14917
  this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
14029
14918
  this.videoSampleDuration = mp4SampleDuration;
@@ -14156,7 +15045,7 @@ class MP4Remuxer {
14156
15045
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
14157
15046
  for (let j = 0; j < missing; j++) {
14158
15047
  const newStamp = Math.max(nextPts, 0);
14159
- let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
15048
+ let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14160
15049
  if (!fillFrame) {
14161
15050
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
14162
15051
  fillFrame = sample.unit.subarray();
@@ -14284,7 +15173,7 @@ class MP4Remuxer {
14284
15173
  // samples count of this segment's duration
14285
15174
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
14286
15175
  // silent frame
14287
- const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
15176
+ const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14288
15177
  logger.warn('[mp4-remuxer]: remux empty Audio');
14289
15178
  // Can't remux if we can't generate a silent frame...
14290
15179
  if (!silentFrame) {
@@ -14678,13 +15567,15 @@ class Transmuxer {
14678
15567
  initSegmentData
14679
15568
  } = transmuxConfig;
14680
15569
  const keyData = getEncryptionType(uintData, decryptdata);
14681
- if (keyData && keyData.method === 'AES-128') {
15570
+ if (keyData && isFullSegmentEncryption(keyData.method)) {
14682
15571
  const decrypter = this.getDecrypter();
15572
+ const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
15573
+
14683
15574
  // Software decryption is synchronous; webCrypto is not
14684
15575
  if (decrypter.isSync()) {
14685
15576
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
14686
15577
  // data is handled in the flush() call
14687
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
15578
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
14688
15579
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
14689
15580
  const loadingParts = chunkMeta.part > -1;
14690
15581
  if (loadingParts) {
@@ -14696,7 +15587,7 @@ class Transmuxer {
14696
15587
  }
14697
15588
  uintData = new Uint8Array(decryptedData);
14698
15589
  } else {
14699
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
15590
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
14700
15591
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
14701
15592
  // the decrypted data has been transmuxed
14702
15593
  const result = this.push(decryptedData, null, chunkMeta);
@@ -15350,14 +16241,7 @@ class TransmuxerInterface {
15350
16241
  this.observer = new EventEmitter();
15351
16242
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
15352
16243
  this.observer.on(Events.ERROR, forwardMessage);
15353
- const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
15354
- isTypeSupported: () => false
15355
- };
15356
- const m2tsTypeSupported = {
15357
- mpeg: MediaSource.isTypeSupported('audio/mpeg'),
15358
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
15359
- ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
15360
- };
16244
+ const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
15361
16245
 
15362
16246
  // navigator.vendor is not always available in Web Worker
15363
16247
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -15645,7 +16529,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
15645
16529
 
15646
16530
  class AudioStreamController extends BaseStreamController {
15647
16531
  constructor(hls, fragmentTracker, keyLoader) {
15648
- super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);
16532
+ super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
15649
16533
  this.videoBuffer = null;
15650
16534
  this.videoTrackCC = -1;
15651
16535
  this.waitingVideoCC = -1;
@@ -15657,27 +16541,24 @@ class AudioStreamController extends BaseStreamController {
15657
16541
  this.flushing = false;
15658
16542
  this.bufferFlushed = false;
15659
16543
  this.cachedTrackLoadedData = null;
15660
- this._registerListeners();
16544
+ this.registerListeners();
15661
16545
  }
15662
16546
  onHandlerDestroying() {
15663
- this._unregisterListeners();
16547
+ this.unregisterListeners();
15664
16548
  super.onHandlerDestroying();
15665
16549
  this.mainDetails = null;
15666
16550
  this.bufferedTrack = null;
15667
16551
  this.switchingTrack = null;
15668
16552
  }
15669
- _registerListeners() {
16553
+ registerListeners() {
16554
+ super.registerListeners();
15670
16555
  const {
15671
16556
  hls
15672
16557
  } = this;
15673
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
15674
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
15675
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
15676
16558
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
15677
16559
  hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
15678
16560
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
15679
16561
  hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15680
- hls.on(Events.ERROR, this.onError, this);
15681
16562
  hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
15682
16563
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
15683
16564
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -15685,18 +16566,18 @@ class AudioStreamController extends BaseStreamController {
15685
16566
  hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
15686
16567
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
15687
16568
  }
15688
- _unregisterListeners() {
16569
+ unregisterListeners() {
15689
16570
  const {
15690
16571
  hls
15691
16572
  } = this;
15692
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
15693
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
15694
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16573
+ if (!hls) {
16574
+ return;
16575
+ }
16576
+ super.unregisterListeners();
15695
16577
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
15696
16578
  hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
15697
16579
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
15698
16580
  hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15699
- hls.off(Events.ERROR, this.onError, this);
15700
16581
  hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
15701
16582
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
15702
16583
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -15839,7 +16720,9 @@ class AudioStreamController extends BaseStreamController {
15839
16720
  this.fragmentTracker.removeFragment(waitingData.frag);
15840
16721
  this.waitingData = null;
15841
16722
  this.waitingVideoCC = -1;
15842
- this.state = State.IDLE;
16723
+ if (this.state !== State.STOPPED) {
16724
+ this.state = State.IDLE;
16725
+ }
15843
16726
  }
15844
16727
  }
15845
16728
  resetLoadingState() {
@@ -15865,12 +16748,13 @@ class AudioStreamController extends BaseStreamController {
15865
16748
  } = this;
15866
16749
  const config = hls.config;
15867
16750
 
15868
- // 1. if video not attached AND
16751
+ // 1. if buffering is suspended
16752
+ // 2. if video not attached AND
15869
16753
  // start fragment already requested OR start frag prefetch not enabled
15870
- // 2. if tracks or track not loaded and selected
16754
+ // 3. if tracks or track not loaded and selected
15871
16755
  // then exit loop
15872
16756
  // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
15873
- if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
16757
+ if (!this.buffering || !media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
15874
16758
  return;
15875
16759
  }
15876
16760
  const levelInfo = levels[trackId];
@@ -15899,9 +16783,8 @@ class AudioStreamController extends BaseStreamController {
15899
16783
  this.state = State.ENDED;
15900
16784
  return;
15901
16785
  }
15902
- const mainBufferInfo = this.getFwdBufferInfo(this.videoBuffer ? this.videoBuffer : this.media, PlaylistLevelType.MAIN);
15903
16786
  const bufferLen = bufferInfo.len;
15904
- const maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len);
16787
+ const maxBufLen = hls.maxBufferLength;
15905
16788
  const fragments = trackDetails.fragments;
15906
16789
  const start = fragments[0].start;
15907
16790
  let targetBufferTime = this.flushing ? this.getLoadPosition() : bufferInfo.end;
@@ -15936,32 +16819,25 @@ class AudioStreamController extends BaseStreamController {
15936
16819
  this.bufferFlushed = true;
15937
16820
  return;
15938
16821
  }
15939
-
15940
- // Buffer audio up to one target duration ahead of main buffer
15941
- const atBufferSyncLimit = mainBufferInfo && frag.start > mainBufferInfo.end + trackDetails.targetduration;
15942
- if (atBufferSyncLimit ||
15943
- // Or wait for main buffer after buffing some audio
15944
- !(mainBufferInfo != null && mainBufferInfo.len) && bufferInfo.len) {
15945
- // Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo
15946
- const mainFrag = this.getAppendedFrag(frag.start, PlaylistLevelType.MAIN);
15947
- if (mainFrag === null) {
15948
- return;
15949
- }
15950
- // Bridge gaps in main buffer
15951
- atGap || (atGap = !!mainFrag.gap || !!atBufferSyncLimit && mainBufferInfo.len === 0);
15952
- if (atBufferSyncLimit && !atGap || atGap && bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end) {
15953
- return;
16822
+ if (!trackDetails.live || targetBufferTime < this.hls.liveSyncPosition) {
16823
+ // Request audio segments up to one fragment ahead of main buffer
16824
+ const mainBufferInfo = this.getFwdBufferInfo(this.videoBuffer ? this.videoBuffer : this.media, PlaylistLevelType.MAIN);
16825
+ const atBufferSyncLimit = !!mainBufferInfo && frag.start > mainBufferInfo.end + frag.duration;
16826
+ if (atBufferSyncLimit) {
16827
+ // Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo
16828
+ const mainFrag = this.fragmentTracker.getFragAtPos(frag.start, PlaylistLevelType.MAIN);
16829
+ if (mainFrag === null) {
16830
+ return;
16831
+ }
16832
+ // Bridge gaps in main buffer (also prevents loop loading at gaps)
16833
+ atGap || (atGap = !!mainFrag.gap || mainBufferInfo.len === 0);
16834
+ if (!atGap || bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end) {
16835
+ return;
16836
+ }
15954
16837
  }
15955
16838
  }
15956
16839
  this.loadFragment(frag, levelInfo, targetBufferTime);
15957
16840
  }
15958
- getMaxBufferLength(mainBufferLength) {
15959
- const maxConfigBuffer = super.getMaxBufferLength();
15960
- if (!mainBufferLength) {
15961
- return maxConfigBuffer;
15962
- }
15963
- return Math.min(Math.max(maxConfigBuffer, mainBufferLength), this.config.maxMaxBufferLength);
15964
- }
15965
16841
  onMediaDetaching() {
15966
16842
  this.videoBuffer = null;
15967
16843
  this.bufferFlushed = this.flushing = false;
@@ -15986,26 +16862,25 @@ class AudioStreamController extends BaseStreamController {
15986
16862
  this.removeUnbufferedFrags(fragCurrent.start);
15987
16863
  }
15988
16864
  this.resetLoadingState();
15989
- // destroy useless transmuxer when switching audio to main
15990
- if (!altAudio) {
15991
- this.resetTransmuxer();
15992
- } else {
15993
- // switching to audio track, start timer if not already started
15994
- this.setInterval(TICK_INTERVAL$2);
15995
- }
15996
16865
 
15997
16866
  // should we switch tracks ?
15998
16867
  if (altAudio) {
15999
16868
  this.switchingTrack = data;
16000
16869
  // main audio track are handled by stream-controller, just do something if switching to alt audio track
16001
- this.state = State.IDLE;
16002
16870
  this.flushAudioIfNeeded(data);
16871
+ if (this.state !== State.STOPPED) {
16872
+ // switching to audio track, start timer if not already started
16873
+ this.setInterval(TICK_INTERVAL$2);
16874
+ this.state = State.IDLE;
16875
+ this.tick();
16876
+ }
16003
16877
  } else {
16878
+ // destroy useless transmuxer when switching audio to main
16879
+ this.resetTransmuxer();
16004
16880
  this.switchingTrack = null;
16005
16881
  this.bufferedTrack = data;
16006
- this.state = State.STOPPED;
16882
+ this.clearInterval();
16007
16883
  }
16008
- this.tick();
16009
16884
  }
16010
16885
  onManifestLoading() {
16011
16886
  this.fragmentTracker.removeAllFragments();
@@ -16428,7 +17303,7 @@ class AudioStreamController extends BaseStreamController {
16428
17303
 
16429
17304
  class AudioTrackController extends BasePlaylistController {
16430
17305
  constructor(hls) {
16431
- super(hls, '[audio-track-controller]');
17306
+ super(hls, 'audio-track-controller');
16432
17307
  this.tracks = [];
16433
17308
  this.groupIds = null;
16434
17309
  this.tracksInGroup = [];
@@ -16747,26 +17622,23 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
16747
17622
 
16748
17623
  class SubtitleStreamController extends BaseStreamController {
16749
17624
  constructor(hls, fragmentTracker, keyLoader) {
16750
- super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);
17625
+ super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
16751
17626
  this.currentTrackId = -1;
16752
17627
  this.tracksBuffered = [];
16753
17628
  this.mainDetails = null;
16754
- this._registerListeners();
17629
+ this.registerListeners();
16755
17630
  }
16756
17631
  onHandlerDestroying() {
16757
- this._unregisterListeners();
17632
+ this.unregisterListeners();
16758
17633
  super.onHandlerDestroying();
16759
17634
  this.mainDetails = null;
16760
17635
  }
16761
- _registerListeners() {
17636
+ registerListeners() {
17637
+ super.registerListeners();
16762
17638
  const {
16763
17639
  hls
16764
17640
  } = this;
16765
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
16766
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
16767
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16768
17641
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16769
- hls.on(Events.ERROR, this.onError, this);
16770
17642
  hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
16771
17643
  hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
16772
17644
  hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -16774,15 +17646,12 @@ class SubtitleStreamController extends BaseStreamController {
16774
17646
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
16775
17647
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
16776
17648
  }
16777
- _unregisterListeners() {
17649
+ unregisterListeners() {
17650
+ super.unregisterListeners();
16778
17651
  const {
16779
17652
  hls
16780
17653
  } = this;
16781
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
16782
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
16783
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16784
17654
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16785
- hls.off(Events.ERROR, this.onError, this);
16786
17655
  hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
16787
17656
  hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
16788
17657
  hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -16928,7 +17797,7 @@ class SubtitleStreamController extends BaseStreamController {
16928
17797
  } else {
16929
17798
  this.mediaBuffer = null;
16930
17799
  }
16931
- if (currentTrack) {
17800
+ if (currentTrack && this.state !== State.STOPPED) {
16932
17801
  this.setInterval(TICK_INTERVAL$1);
16933
17802
  }
16934
17803
  }
@@ -17009,10 +17878,10 @@ class SubtitleStreamController extends BaseStreamController {
17009
17878
  return;
17010
17879
  }
17011
17880
  // check to see if the payload needs to be decrypted
17012
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
17881
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
17013
17882
  const startTime = performance.now();
17014
17883
  // decrypt the subtitles
17015
- this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
17884
+ this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
17016
17885
  hls.trigger(Events.ERROR, {
17017
17886
  type: ErrorTypes.MEDIA_ERROR,
17018
17887
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -17061,9 +17930,8 @@ class SubtitleStreamController extends BaseStreamController {
17061
17930
  end: targetBufferTime,
17062
17931
  len: bufferLen
17063
17932
  } = bufferedInfo;
17064
- const mainBufferInfo = this.getFwdBufferInfo(this.media, PlaylistLevelType.MAIN);
17065
17933
  const trackDetails = track.details;
17066
- const maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len) + trackDetails.levelTargetDuration;
17934
+ const maxBufLen = this.hls.maxBufferLength + trackDetails.levelTargetDuration;
17067
17935
  if (bufferLen > maxBufLen) {
17068
17936
  return;
17069
17937
  }
@@ -17100,13 +17968,6 @@ class SubtitleStreamController extends BaseStreamController {
17100
17968
  }
17101
17969
  }
17102
17970
  }
17103
- getMaxBufferLength(mainBufferLength) {
17104
- const maxConfigBuffer = super.getMaxBufferLength();
17105
- if (!mainBufferLength) {
17106
- return maxConfigBuffer;
17107
- }
17108
- return Math.max(maxConfigBuffer, mainBufferLength);
17109
- }
17110
17971
  loadFragment(frag, level, targetBufferTime) {
17111
17972
  this.fragCurrent = frag;
17112
17973
  if (frag.sn === 'initSegment') {
@@ -17146,7 +18007,7 @@ class BufferableInstance {
17146
18007
 
17147
18008
  class SubtitleTrackController extends BasePlaylistController {
17148
18009
  constructor(hls) {
17149
- super(hls, '[subtitle-track-controller]');
18010
+ super(hls, 'subtitle-track-controller');
17150
18011
  this.media = null;
17151
18012
  this.tracks = [];
17152
18013
  this.groupIds = null;
@@ -17155,10 +18016,10 @@ class SubtitleTrackController extends BasePlaylistController {
17155
18016
  this.currentTrack = null;
17156
18017
  this.selectDefaultTrack = true;
17157
18018
  this.queuedDefaultTrack = -1;
17158
- this.asyncPollTrackChange = () => this.pollTrackChange(0);
17159
18019
  this.useTextTrackPolling = false;
17160
18020
  this.subtitlePollingInterval = -1;
17161
18021
  this._subtitleDisplay = true;
18022
+ this.asyncPollTrackChange = () => this.pollTrackChange(0);
17162
18023
  this.onTextTracksChanged = () => {
17163
18024
  if (!this.useTextTrackPolling) {
17164
18025
  self.clearInterval(this.subtitlePollingInterval);
@@ -17192,6 +18053,7 @@ class SubtitleTrackController extends BasePlaylistController {
17192
18053
  this.tracks.length = 0;
17193
18054
  this.tracksInGroup.length = 0;
17194
18055
  this.currentTrack = null;
18056
+ // @ts-ignore
17195
18057
  this.onTextTracksChanged = this.asyncPollTrackChange = null;
17196
18058
  super.destroy();
17197
18059
  }
@@ -17603,24 +18465,22 @@ class BufferOperationQueue {
17603
18465
  this.executeNext(type);
17604
18466
  }
17605
18467
  }
17606
- insertAbort(operation, type) {
17607
- const queue = this.queues[type];
17608
- queue.unshift(operation);
17609
- this.executeNext(type);
17610
- }
17611
18468
  appendBlocker(type) {
17612
- let execute;
17613
- const promise = new Promise(resolve => {
17614
- execute = resolve;
18469
+ return new Promise(resolve => {
18470
+ const operation = {
18471
+ execute: resolve,
18472
+ onStart: () => {},
18473
+ onComplete: () => {},
18474
+ onError: () => {}
18475
+ };
18476
+ this.append(operation, type);
17615
18477
  });
17616
- const operation = {
17617
- execute,
17618
- onStart: () => {},
17619
- onComplete: () => {},
17620
- onError: () => {}
17621
- };
17622
- this.append(operation, type);
17623
- return promise;
18478
+ }
18479
+ unblockAudio(op) {
18480
+ const queue = this.queues.audio;
18481
+ if (queue[0] === op) {
18482
+ this.shiftAndExecuteNext('audio');
18483
+ }
17624
18484
  }
17625
18485
  executeNext(type) {
17626
18486
  const queue = this.queues[type];
@@ -17652,8 +18512,9 @@ class BufferOperationQueue {
17652
18512
  }
17653
18513
 
17654
18514
  const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
17655
- class BufferController {
17656
- constructor(hls) {
18515
+ class BufferController extends Logger {
18516
+ constructor(hls, fragmentTracker) {
18517
+ super('buffer-controller', hls.logger);
17657
18518
  // The level details used to determine duration, target-duration and live
17658
18519
  this.details = null;
17659
18520
  // cache the self generated object url to detect hijack of video tag
@@ -17663,6 +18524,7 @@ class BufferController {
17663
18524
  // References to event listeners for each SourceBuffer, so that they can be referenced for event removal
17664
18525
  this.listeners = void 0;
17665
18526
  this.hls = void 0;
18527
+ this.fragmentTracker = void 0;
17666
18528
  // The number of BUFFER_CODEC events received before any sourceBuffers are created
17667
18529
  this.bufferCodecEventsExpected = 0;
17668
18530
  // The total number of BUFFER_CODEC events received
@@ -17673,6 +18535,10 @@ class BufferController {
17673
18535
  this.mediaSource = null;
17674
18536
  // Last MP3 audio chunk appended
17675
18537
  this.lastMpegAudioChunk = null;
18538
+ // Audio fragment blocked from appending until corresponding video appends or context changes
18539
+ this.blockedAudioAppend = null;
18540
+ // Keep track of video append position for unblocking audio
18541
+ this.lastVideoAppendEnd = 0;
17676
18542
  this.appendSource = void 0;
17677
18543
  // counters
17678
18544
  this.appendErrors = {
@@ -17683,9 +18549,6 @@ class BufferController {
17683
18549
  this.tracks = {};
17684
18550
  this.pendingTracks = {};
17685
18551
  this.sourceBuffer = void 0;
17686
- this.log = void 0;
17687
- this.warn = void 0;
17688
- this.error = void 0;
17689
18552
  this._onEndStreaming = event => {
17690
18553
  if (!this.hls) {
17691
18554
  return;
@@ -17707,7 +18570,10 @@ class BufferController {
17707
18570
  this.log('Media source opened');
17708
18571
  if (media) {
17709
18572
  media.removeEventListener('emptied', this._onMediaEmptied);
17710
- this.updateMediaElementDuration();
18573
+ const durationAndRange = this.getDurationAndRange();
18574
+ if (durationAndRange) {
18575
+ this.updateMediaSource(durationAndRange);
18576
+ }
17711
18577
  this.hls.trigger(Events.MEDIA_ATTACHED, {
17712
18578
  media,
17713
18579
  mediaSource: mediaSource
@@ -17731,15 +18597,12 @@ class BufferController {
17731
18597
  _objectUrl
17732
18598
  } = this;
17733
18599
  if (mediaSrc !== _objectUrl) {
17734
- logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
18600
+ this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
17735
18601
  }
17736
18602
  };
17737
18603
  this.hls = hls;
17738
- const logPrefix = '[buffer-controller]';
18604
+ this.fragmentTracker = fragmentTracker;
17739
18605
  this.appendSource = hls.config.preferManagedMediaSource && typeof self !== 'undefined' && self.ManagedMediaSource;
17740
- this.log = logger.log.bind(logger, logPrefix);
17741
- this.warn = logger.warn.bind(logger, logPrefix);
17742
- this.error = logger.error.bind(logger, logPrefix);
17743
18606
  this._initSourceBuffer();
17744
18607
  this.registerListeners();
17745
18608
  }
@@ -17751,7 +18614,13 @@ class BufferController {
17751
18614
  this.details = null;
17752
18615
  this.lastMpegAudioChunk = null;
17753
18616
  // @ts-ignore
17754
- this.hls = null;
18617
+ this.hls = this.fragmentTracker = null;
18618
+ // @ts-ignore
18619
+ this._onMediaSourceOpen = this._onMediaSourceClose = null;
18620
+ // @ts-ignore
18621
+ this._onMediaSourceEnded = null;
18622
+ // @ts-ignore
18623
+ this._onStartStreaming = this._onEndStreaming = null;
17755
18624
  }
17756
18625
  registerListeners() {
17757
18626
  const {
@@ -17801,6 +18670,8 @@ class BufferController {
17801
18670
  audiovideo: 0
17802
18671
  };
17803
18672
  this.lastMpegAudioChunk = null;
18673
+ this.blockedAudioAppend = null;
18674
+ this.lastVideoAppendEnd = 0;
17804
18675
  }
17805
18676
  onManifestLoading() {
17806
18677
  this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = 0;
@@ -17918,6 +18789,7 @@ class BufferController {
17918
18789
  this.resetBuffer(type);
17919
18790
  });
17920
18791
  this._initSourceBuffer();
18792
+ this.hls.resumeBuffering();
17921
18793
  }
17922
18794
  resetBuffer(type) {
17923
18795
  const sb = this.sourceBuffer[type];
@@ -17941,9 +18813,10 @@ class BufferController {
17941
18813
  const trackNames = Object.keys(data);
17942
18814
  trackNames.forEach(trackName => {
17943
18815
  if (sourceBufferCount) {
18816
+ var _track$buffer;
17944
18817
  // check if SourceBuffer codec needs to change
17945
18818
  const track = this.tracks[trackName];
17946
- if (track && typeof track.buffer.changeType === 'function') {
18819
+ if (track && typeof ((_track$buffer = track.buffer) == null ? void 0 : _track$buffer.changeType) === 'function') {
17947
18820
  var _trackCodec;
17948
18821
  const {
17949
18822
  id,
@@ -18013,20 +18886,54 @@ class BufferController {
18013
18886
  };
18014
18887
  operationQueue.append(operation, type, !!this.pendingTracks[type]);
18015
18888
  }
18889
+ blockAudio(partOrFrag) {
18890
+ var _this$fragmentTracker;
18891
+ const pStart = partOrFrag.start;
18892
+ const pTime = pStart + partOrFrag.duration * 0.05;
18893
+ const atGap = ((_this$fragmentTracker = this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker.gap) === true;
18894
+ if (atGap) {
18895
+ return;
18896
+ }
18897
+ const op = {
18898
+ execute: () => {
18899
+ var _this$fragmentTracker2;
18900
+ if (this.lastVideoAppendEnd > pTime || this.sourceBuffer.video && BufferHelper.isBuffered(this.sourceBuffer.video, pTime) || ((_this$fragmentTracker2 = this.fragmentTracker.getAppendedFrag(pTime, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker2.gap) === true) {
18901
+ this.blockedAudioAppend = null;
18902
+ this.operationQueue.shiftAndExecuteNext('audio');
18903
+ }
18904
+ },
18905
+ onStart: () => {},
18906
+ onComplete: () => {},
18907
+ onError: () => {}
18908
+ };
18909
+ this.blockedAudioAppend = {
18910
+ op,
18911
+ frag: partOrFrag
18912
+ };
18913
+ this.operationQueue.append(op, 'audio', true);
18914
+ }
18915
+ unblockAudio() {
18916
+ const blockedAudioAppend = this.blockedAudioAppend;
18917
+ if (blockedAudioAppend) {
18918
+ this.blockedAudioAppend = null;
18919
+ this.operationQueue.unblockAudio(blockedAudioAppend.op);
18920
+ }
18921
+ }
18016
18922
  onBufferAppending(event, eventData) {
18017
18923
  const {
18018
- hls,
18019
18924
  operationQueue,
18020
18925
  tracks
18021
18926
  } = this;
18022
18927
  const {
18023
18928
  data,
18024
18929
  type,
18930
+ parent,
18025
18931
  frag,
18026
18932
  part,
18027
18933
  chunkMeta
18028
18934
  } = eventData;
18029
18935
  const chunkStats = chunkMeta.buffering[type];
18936
+ const sn = frag.sn;
18030
18937
  const bufferAppendingStart = self.performance.now();
18031
18938
  chunkStats.start = bufferAppendingStart;
18032
18939
  const fragBuffering = frag.stats.buffering;
@@ -18049,7 +18956,36 @@ class BufferController {
18049
18956
  checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn;
18050
18957
  this.lastMpegAudioChunk = chunkMeta;
18051
18958
  }
18052
- const fragStart = frag.start;
18959
+
18960
+ // Block audio append until overlapping video append
18961
+ const videoSb = this.sourceBuffer.video;
18962
+ if (videoSb && sn !== 'initSegment') {
18963
+ const partOrFrag = part || frag;
18964
+ const blockedAudioAppend = this.blockedAudioAppend;
18965
+ if (type === 'audio' && parent !== 'main' && !this.blockedAudioAppend) {
18966
+ const pStart = partOrFrag.start;
18967
+ const pTime = pStart + partOrFrag.duration * 0.05;
18968
+ const vbuffered = videoSb.buffered;
18969
+ const vappending = this.operationQueue.current('video');
18970
+ if (!vbuffered.length && !vappending) {
18971
+ // wait for video before appending audio
18972
+ this.blockAudio(partOrFrag);
18973
+ } else if (!vappending && !BufferHelper.isBuffered(videoSb, pTime) && this.lastVideoAppendEnd < pTime) {
18974
+ // audio is ahead of video
18975
+ this.blockAudio(partOrFrag);
18976
+ }
18977
+ } else if (type === 'video') {
18978
+ const videoAppendEnd = partOrFrag.end;
18979
+ if (blockedAudioAppend) {
18980
+ const audioStart = blockedAudioAppend.frag.start;
18981
+ if (videoAppendEnd > audioStart || videoAppendEnd < this.lastVideoAppendEnd || BufferHelper.isBuffered(videoSb, audioStart)) {
18982
+ this.unblockAudio();
18983
+ }
18984
+ }
18985
+ this.lastVideoAppendEnd = videoAppendEnd;
18986
+ }
18987
+ }
18988
+ const fragStart = (part || frag).start;
18053
18989
  const operation = {
18054
18990
  execute: () => {
18055
18991
  chunkStats.executeStart = self.performance.now();
@@ -18058,7 +18994,7 @@ class BufferController {
18058
18994
  if (sb) {
18059
18995
  const delta = fragStart - sb.timestampOffset;
18060
18996
  if (Math.abs(delta) >= 0.1) {
18061
- this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${frag.sn})`);
18997
+ this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`);
18062
18998
  sb.timestampOffset = fragStart;
18063
18999
  }
18064
19000
  }
@@ -18125,22 +19061,21 @@ class BufferController {
18125
19061
  /* with UHD content, we could get loop of quota exceeded error until
18126
19062
  browser is able to evict some data from sourcebuffer. Retrying can help recover.
18127
19063
  */
18128
- this.warn(`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
18129
- if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
19064
+ this.warn(`Failed ${appendErrorCount}/${this.hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
19065
+ if (appendErrorCount >= this.hls.config.appendErrorMaxRetry) {
18130
19066
  event.fatal = true;
18131
19067
  }
18132
19068
  }
18133
- hls.trigger(Events.ERROR, event);
19069
+ this.hls.trigger(Events.ERROR, event);
18134
19070
  }
18135
19071
  };
18136
19072
  operationQueue.append(operation, type, !!this.pendingTracks[type]);
18137
19073
  }
18138
- onBufferFlushing(event, data) {
18139
- const {
18140
- operationQueue
18141
- } = this;
18142
- const flushOperation = type => ({
18143
- execute: this.removeExecutor.bind(this, type, data.startOffset, data.endOffset),
19074
+ getFlushOp(type, start, end) {
19075
+ return {
19076
+ execute: () => {
19077
+ this.removeExecutor(type, start, end);
19078
+ },
18144
19079
  onStart: () => {
18145
19080
  // logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
18146
19081
  },
@@ -18153,12 +19088,22 @@ class BufferController {
18153
19088
  onError: error => {
18154
19089
  this.warn(`Failed to remove from ${type} SourceBuffer`, error);
18155
19090
  }
18156
- });
18157
- if (data.type) {
18158
- operationQueue.append(flushOperation(data.type), data.type);
19091
+ };
19092
+ }
19093
+ onBufferFlushing(event, data) {
19094
+ const {
19095
+ operationQueue
19096
+ } = this;
19097
+ const {
19098
+ type,
19099
+ startOffset,
19100
+ endOffset
19101
+ } = data;
19102
+ if (type) {
19103
+ operationQueue.append(this.getFlushOp(type, startOffset, endOffset), type);
18159
19104
  } else {
18160
- this.getSourceBufferTypes().forEach(type => {
18161
- operationQueue.append(flushOperation(type), type);
19105
+ this.getSourceBufferTypes().forEach(sbType => {
19106
+ operationQueue.append(this.getFlushOp(sbType, startOffset, endOffset), sbType);
18162
19107
  });
18163
19108
  }
18164
19109
  }
@@ -18205,6 +19150,9 @@ class BufferController {
18205
19150
  // on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
18206
19151
  // an undefined data.type will mark all buffers as EOS.
18207
19152
  onBufferEos(event, data) {
19153
+ if (data.type === 'video') {
19154
+ this.unblockAudio();
19155
+ }
18208
19156
  const ended = this.getSourceBufferTypes().reduce((acc, type) => {
18209
19157
  const sb = this.sourceBuffer[type];
18210
19158
  if (sb && (!data.type || data.type === type)) {
@@ -18247,10 +19195,14 @@ class BufferController {
18247
19195
  return;
18248
19196
  }
18249
19197
  this.details = details;
19198
+ const durationAndRange = this.getDurationAndRange();
19199
+ if (!durationAndRange) {
19200
+ return;
19201
+ }
18250
19202
  if (this.getSourceBufferTypes().length) {
18251
- this.blockBuffers(this.updateMediaElementDuration.bind(this));
19203
+ this.blockBuffers(() => this.updateMediaSource(durationAndRange));
18252
19204
  } else {
18253
- this.updateMediaElementDuration();
19205
+ this.updateMediaSource(durationAndRange);
18254
19206
  }
18255
19207
  }
18256
19208
  trimBuffers() {
@@ -18355,9 +19307,9 @@ class BufferController {
18355
19307
  * 'liveDurationInfinity` is set to `true`
18356
19308
  * More details: https://github.com/video-dev/hls.js/issues/355
18357
19309
  */
18358
- updateMediaElementDuration() {
19310
+ getDurationAndRange() {
18359
19311
  if (!this.details || !this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
18360
- return;
19312
+ return null;
18361
19313
  }
18362
19314
  const {
18363
19315
  details,
@@ -18371,25 +19323,41 @@ class BufferController {
18371
19323
  if (details.live && hls.config.liveDurationInfinity) {
18372
19324
  // Override duration to Infinity
18373
19325
  mediaSource.duration = Infinity;
18374
- this.updateSeekableRange(details);
19326
+ const len = details.fragments.length;
19327
+ if (len && details.live && !!mediaSource.setLiveSeekableRange) {
19328
+ const start = Math.max(0, details.fragments[0].start);
19329
+ const end = Math.max(start, start + details.totalduration);
19330
+ return {
19331
+ duration: Infinity,
19332
+ start,
19333
+ end
19334
+ };
19335
+ }
19336
+ return {
19337
+ duration: Infinity
19338
+ };
18375
19339
  } else if (levelDuration > msDuration && levelDuration > mediaDuration || !isFiniteNumber(mediaDuration)) {
18376
- // levelDuration was the last value we set.
18377
- // not using mediaSource.duration as the browser may tweak this value
18378
- // only update Media Source duration if its value increase, this is to avoid
18379
- // flushing already buffered portion when switching between quality level
18380
- this.log(`Updating Media Source duration to ${levelDuration.toFixed(3)}`);
18381
- mediaSource.duration = levelDuration;
19340
+ return {
19341
+ duration: levelDuration
19342
+ };
18382
19343
  }
19344
+ return null;
18383
19345
  }
18384
- updateSeekableRange(levelDetails) {
18385
- const mediaSource = this.mediaSource;
18386
- const fragments = levelDetails.fragments;
18387
- const len = fragments.length;
18388
- if (len && levelDetails.live && mediaSource != null && mediaSource.setLiveSeekableRange) {
18389
- const start = Math.max(0, fragments[0].start);
18390
- const end = Math.max(start, start + levelDetails.totalduration);
18391
- this.log(`Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
18392
- mediaSource.setLiveSeekableRange(start, end);
19346
+ updateMediaSource({
19347
+ duration,
19348
+ start,
19349
+ end
19350
+ }) {
19351
+ if (!this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
19352
+ return;
19353
+ }
19354
+ if (isFiniteNumber(duration)) {
19355
+ this.log(`Updating Media Source duration to ${duration.toFixed(3)}`);
19356
+ }
19357
+ this.mediaSource.duration = duration;
19358
+ if (start !== undefined && end !== undefined) {
19359
+ this.log(`Media Source duration is set to ${this.mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
19360
+ this.mediaSource.setLiveSeekableRange(start, end);
18393
19361
  }
18394
19362
  }
18395
19363
  checkPendingTracks() {
@@ -18574,6 +19542,7 @@ class BufferController {
18574
19542
  }
18575
19543
  return;
18576
19544
  }
19545
+ sb.ending = false;
18577
19546
  sb.ended = false;
18578
19547
  sb.appendBuffer(data);
18579
19548
  }
@@ -18593,10 +19562,14 @@ class BufferController {
18593
19562
 
18594
19563
  // logger.debug(`[buffer-controller]: Blocking ${buffers} SourceBuffer`);
18595
19564
  const blockingOperations = buffers.map(type => operationQueue.appendBlocker(type));
18596
- Promise.all(blockingOperations).then(() => {
19565
+ const audioBlocked = buffers.length > 1 && !!this.blockedAudioAppend;
19566
+ if (audioBlocked) {
19567
+ this.unblockAudio();
19568
+ }
19569
+ Promise.all(blockingOperations).then(result => {
18597
19570
  // logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
18598
19571
  onUnblocked();
18599
- buffers.forEach(type => {
19572
+ buffers.forEach((type, i) => {
18600
19573
  const sb = this.sourceBuffer[type];
18601
19574
  // Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
18602
19575
  // true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration)
@@ -21022,14 +21995,12 @@ class TimelineController {
21022
21995
  this.cea608Parser1 = this.cea608Parser2 = undefined;
21023
21996
  }
21024
21997
  initCea608Parsers() {
21025
- if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) {
21026
- const channel1 = new OutputFilter(this, 'textTrack1');
21027
- const channel2 = new OutputFilter(this, 'textTrack2');
21028
- const channel3 = new OutputFilter(this, 'textTrack3');
21029
- const channel4 = new OutputFilter(this, 'textTrack4');
21030
- this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
21031
- this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
21032
- }
21998
+ const channel1 = new OutputFilter(this, 'textTrack1');
21999
+ const channel2 = new OutputFilter(this, 'textTrack2');
22000
+ const channel3 = new OutputFilter(this, 'textTrack3');
22001
+ const channel4 = new OutputFilter(this, 'textTrack4');
22002
+ this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
22003
+ this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
21033
22004
  }
21034
22005
  addCues(trackName, startTime, endTime, screen, cueRanges) {
21035
22006
  // skip cues which overlap more than 50% with previously parsed time ranges
@@ -21267,7 +22238,7 @@ class TimelineController {
21267
22238
  if (inUseTracks != null && inUseTracks.length) {
21268
22239
  const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
21269
22240
  if (unusedTextTracks.length) {
21270
- logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
22241
+ this.hls.logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
21271
22242
  }
21272
22243
  }
21273
22244
  } else if (this.tracks.length) {
@@ -21312,26 +22283,23 @@ class TimelineController {
21312
22283
  return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
21313
22284
  }
21314
22285
  onFragLoading(event, data) {
21315
- this.initCea608Parsers();
21316
- const {
21317
- cea608Parser1,
21318
- cea608Parser2,
21319
- lastCc,
21320
- lastSn,
21321
- lastPartIndex
21322
- } = this;
21323
- if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
21324
- return;
21325
- }
21326
22286
  // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
21327
- if (data.frag.type === PlaylistLevelType.MAIN) {
22287
+ if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
21328
22288
  var _data$part$index, _data$part;
22289
+ const {
22290
+ cea608Parser1,
22291
+ cea608Parser2,
22292
+ lastSn
22293
+ } = this;
22294
+ if (!cea608Parser1 || !cea608Parser2) {
22295
+ return;
22296
+ }
21329
22297
  const {
21330
22298
  cc,
21331
22299
  sn
21332
22300
  } = data.frag;
21333
- const partIndex = (_data$part$index = data == null ? void 0 : (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
21334
- if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
22301
+ const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
22302
+ if (!(sn === lastSn + 1 || sn === lastSn && partIndex === this.lastPartIndex + 1 || cc === this.lastCc)) {
21335
22303
  cea608Parser1.reset();
21336
22304
  cea608Parser2.reset();
21337
22305
  }
@@ -21388,7 +22356,7 @@ class TimelineController {
21388
22356
  frag: frag
21389
22357
  });
21390
22358
  }, error => {
21391
- logger.log(`Failed to parse IMSC1: ${error}`);
22359
+ hls.logger.log(`Failed to parse IMSC1: ${error}`);
21392
22360
  hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
21393
22361
  success: false,
21394
22362
  frag: frag,
@@ -21429,7 +22397,7 @@ class TimelineController {
21429
22397
  this._fallbackToIMSC1(frag, payload);
21430
22398
  }
21431
22399
  // Something went wrong while parsing. Trigger event with success false.
21432
- logger.log(`Failed to parse VTT cue: ${error}`);
22400
+ hls.logger.log(`Failed to parse VTT cue: ${error}`);
21433
22401
  if (missingInitPTS && maxAvCC > frag.cc) {
21434
22402
  return;
21435
22403
  }
@@ -21490,12 +22458,7 @@ class TimelineController {
21490
22458
  this.captionsTracks = {};
21491
22459
  }
21492
22460
  onFragParsingUserdata(event, data) {
21493
- this.initCea608Parsers();
21494
- const {
21495
- cea608Parser1,
21496
- cea608Parser2
21497
- } = this;
21498
- if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
22461
+ if (!this.enabled || !this.config.enableCEA708Captions) {
21499
22462
  return;
21500
22463
  }
21501
22464
  const {
@@ -21510,9 +22473,12 @@ class TimelineController {
21510
22473
  for (let i = 0; i < samples.length; i++) {
21511
22474
  const ccBytes = samples[i].bytes;
21512
22475
  if (ccBytes) {
22476
+ if (!this.cea608Parser1) {
22477
+ this.initCea608Parsers();
22478
+ }
21513
22479
  const ccdatas = this.extractCea608Data(ccBytes);
21514
- cea608Parser1.addData(samples[i].pts, ccdatas[0]);
21515
- cea608Parser2.addData(samples[i].pts, ccdatas[1]);
22480
+ this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
22481
+ this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
21516
22482
  }
21517
22483
  }
21518
22484
  }
@@ -21708,10 +22674,10 @@ class CapLevelController {
21708
22674
  const hls = this.hls;
21709
22675
  const maxLevel = this.getMaxLevel(levels.length - 1);
21710
22676
  if (maxLevel !== this.autoLevelCapping) {
21711
- logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
22677
+ hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
21712
22678
  }
21713
22679
  hls.autoLevelCapping = maxLevel;
21714
- if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
22680
+ if (hls.autoLevelEnabled && hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
21715
22681
  // if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch
21716
22682
  // usually happen when the user go to the fullscreen mode.
21717
22683
  this.streamController.nextLevelSwitch();
@@ -21886,10 +22852,10 @@ class FPSController {
21886
22852
  totalDroppedFrames: droppedFrames
21887
22853
  });
21888
22854
  if (droppedFPS > 0) {
21889
- // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
22855
+ // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
21890
22856
  if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
21891
22857
  let currentLevel = hls.currentLevel;
21892
- logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
22858
+ hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
21893
22859
  if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
21894
22860
  currentLevel = currentLevel - 1;
21895
22861
  hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
@@ -21921,7 +22887,6 @@ class FPSController {
21921
22887
  }
21922
22888
  }
21923
22889
 
21924
- const LOGGER_PREFIX = '[eme]';
21925
22890
  /**
21926
22891
  * Controller to deal with encrypted media extensions (EME)
21927
22892
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
@@ -21929,8 +22894,9 @@ const LOGGER_PREFIX = '[eme]';
21929
22894
  * @class
21930
22895
  * @constructor
21931
22896
  */
21932
- class EMEController {
22897
+ class EMEController extends Logger {
21933
22898
  constructor(hls) {
22899
+ super('eme', hls.logger);
21934
22900
  this.hls = void 0;
21935
22901
  this.config = void 0;
21936
22902
  this.media = null;
@@ -21940,12 +22906,100 @@ class EMEController {
21940
22906
  this.mediaKeySessions = [];
21941
22907
  this.keyIdToKeySessionPromise = {};
21942
22908
  this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
21943
- this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
21944
- this.onWaitingForKey = this._onWaitingForKey.bind(this);
21945
- this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
21946
- this.log = logger.log.bind(logger, LOGGER_PREFIX);
21947
- this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
21948
- this.error = logger.error.bind(logger, LOGGER_PREFIX);
22909
+ this.onMediaEncrypted = event => {
22910
+ const {
22911
+ initDataType,
22912
+ initData
22913
+ } = event;
22914
+ this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22915
+
22916
+ // Ignore event when initData is null
22917
+ if (initData === null) {
22918
+ return;
22919
+ }
22920
+ let keyId;
22921
+ let keySystemDomain;
22922
+ if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22923
+ // Match sinf keyId to playlist skd://keyId=
22924
+ const json = bin2str(new Uint8Array(initData));
22925
+ try {
22926
+ const sinf = base64Decode(JSON.parse(json).sinf);
22927
+ const tenc = parseSinf(new Uint8Array(sinf));
22928
+ if (!tenc) {
22929
+ return;
22930
+ }
22931
+ keyId = tenc.subarray(8, 24);
22932
+ keySystemDomain = KeySystems.FAIRPLAY;
22933
+ } catch (error) {
22934
+ this.warn('Failed to parse sinf "encrypted" event message initData');
22935
+ return;
22936
+ }
22937
+ } else {
22938
+ // Support clear-lead key-session creation (otherwise depend on playlist keys)
22939
+ const psshInfo = parsePssh(initData);
22940
+ if (psshInfo === null) {
22941
+ return;
22942
+ }
22943
+ if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22944
+ keyId = psshInfo.data.subarray(8, 24);
22945
+ }
22946
+ keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22947
+ }
22948
+ if (!keySystemDomain || !keyId) {
22949
+ return;
22950
+ }
22951
+ const keyIdHex = Hex.hexDump(keyId);
22952
+ const {
22953
+ keyIdToKeySessionPromise,
22954
+ mediaKeySessions
22955
+ } = this;
22956
+ let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22957
+ for (let i = 0; i < mediaKeySessions.length; i++) {
22958
+ // Match playlist key
22959
+ const keyContext = mediaKeySessions[i];
22960
+ const decryptdata = keyContext.decryptdata;
22961
+ if (decryptdata.pssh || !decryptdata.keyId) {
22962
+ continue;
22963
+ }
22964
+ const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22965
+ if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22966
+ keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22967
+ delete keyIdToKeySessionPromise[oldKeyIdHex];
22968
+ decryptdata.pssh = new Uint8Array(initData);
22969
+ decryptdata.keyId = keyId;
22970
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22971
+ return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22972
+ });
22973
+ break;
22974
+ }
22975
+ }
22976
+ if (!keySessionContextPromise) {
22977
+ // Clear-lead key (not encountered in playlist)
22978
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22979
+ keySystem,
22980
+ mediaKeys
22981
+ }) => {
22982
+ var _keySystemToKeySystem;
22983
+ this.throwIfDestroyed();
22984
+ const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22985
+ decryptdata.pssh = new Uint8Array(initData);
22986
+ decryptdata.keyId = keyId;
22987
+ return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22988
+ this.throwIfDestroyed();
22989
+ const keySessionContext = this.createMediaKeySessionContext({
22990
+ decryptdata,
22991
+ keySystem,
22992
+ mediaKeys
22993
+ });
22994
+ return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22995
+ });
22996
+ });
22997
+ }
22998
+ keySessionContextPromise.catch(error => this.handleError(error));
22999
+ };
23000
+ this.onWaitingForKey = event => {
23001
+ this.log(`"${event.type}" event`);
23002
+ };
21949
23003
  this.hls = hls;
21950
23004
  this.config = hls.config;
21951
23005
  this.registerListeners();
@@ -21959,9 +23013,9 @@ class EMEController {
21959
23013
  config.licenseXhrSetup = config.licenseResponseCallback = undefined;
21960
23014
  config.drmSystems = config.drmSystemOptions = {};
21961
23015
  // @ts-ignore
21962
- this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
23016
+ this.hls = this.config = this.keyIdToKeySessionPromise = null;
21963
23017
  // @ts-ignore
21964
- this.config = null;
23018
+ this.onMediaEncrypted = this.onWaitingForKey = null;
21965
23019
  }
21966
23020
  registerListeners() {
21967
23021
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -22225,100 +23279,6 @@ class EMEController {
22225
23279
  }
22226
23280
  return this.attemptKeySystemAccess(keySystemsToAttempt);
22227
23281
  }
22228
- _onMediaEncrypted(event) {
22229
- const {
22230
- initDataType,
22231
- initData
22232
- } = event;
22233
- this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22234
-
22235
- // Ignore event when initData is null
22236
- if (initData === null) {
22237
- return;
22238
- }
22239
- let keyId;
22240
- let keySystemDomain;
22241
- if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22242
- // Match sinf keyId to playlist skd://keyId=
22243
- const json = bin2str(new Uint8Array(initData));
22244
- try {
22245
- const sinf = base64Decode(JSON.parse(json).sinf);
22246
- const tenc = parseSinf(new Uint8Array(sinf));
22247
- if (!tenc) {
22248
- return;
22249
- }
22250
- keyId = tenc.subarray(8, 24);
22251
- keySystemDomain = KeySystems.FAIRPLAY;
22252
- } catch (error) {
22253
- this.warn('Failed to parse sinf "encrypted" event message initData');
22254
- return;
22255
- }
22256
- } else {
22257
- // Support clear-lead key-session creation (otherwise depend on playlist keys)
22258
- const psshInfo = parsePssh(initData);
22259
- if (psshInfo === null) {
22260
- return;
22261
- }
22262
- if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22263
- keyId = psshInfo.data.subarray(8, 24);
22264
- }
22265
- keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22266
- }
22267
- if (!keySystemDomain || !keyId) {
22268
- return;
22269
- }
22270
- const keyIdHex = Hex.hexDump(keyId);
22271
- const {
22272
- keyIdToKeySessionPromise,
22273
- mediaKeySessions
22274
- } = this;
22275
- let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22276
- for (let i = 0; i < mediaKeySessions.length; i++) {
22277
- // Match playlist key
22278
- const keyContext = mediaKeySessions[i];
22279
- const decryptdata = keyContext.decryptdata;
22280
- if (decryptdata.pssh || !decryptdata.keyId) {
22281
- continue;
22282
- }
22283
- const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22284
- if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22285
- keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22286
- delete keyIdToKeySessionPromise[oldKeyIdHex];
22287
- decryptdata.pssh = new Uint8Array(initData);
22288
- decryptdata.keyId = keyId;
22289
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22290
- return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22291
- });
22292
- break;
22293
- }
22294
- }
22295
- if (!keySessionContextPromise) {
22296
- // Clear-lead key (not encountered in playlist)
22297
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22298
- keySystem,
22299
- mediaKeys
22300
- }) => {
22301
- var _keySystemToKeySystem;
22302
- this.throwIfDestroyed();
22303
- const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22304
- decryptdata.pssh = new Uint8Array(initData);
22305
- decryptdata.keyId = keyId;
22306
- return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22307
- this.throwIfDestroyed();
22308
- const keySessionContext = this.createMediaKeySessionContext({
22309
- decryptdata,
22310
- keySystem,
22311
- mediaKeys
22312
- });
22313
- return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22314
- });
22315
- });
22316
- }
22317
- keySessionContextPromise.catch(error => this.handleError(error));
22318
- }
22319
- _onWaitingForKey(event) {
22320
- this.log(`"${event.type}" event`);
22321
- }
22322
23282
  attemptSetMediaKeys(keySystem, mediaKeys) {
22323
23283
  const queue = this.setMediaKeysQueue.slice();
22324
23284
  this.log(`Setting media-keys for "${keySystem}"`);
@@ -22911,20 +23871,6 @@ class SfItem {
22911
23871
  }
22912
23872
  }
22913
23873
 
22914
- /**
22915
- * A class to represent structured field tokens when `Symbol` is not available.
22916
- *
22917
- * @group Structured Field
22918
- *
22919
- * @beta
22920
- */
22921
- class SfToken {
22922
- constructor(description) {
22923
- this.description = void 0;
22924
- this.description = description;
22925
- }
22926
- }
22927
-
22928
23874
  const DICT = 'Dict';
22929
23875
 
22930
23876
  function format(value) {
@@ -22948,29 +23894,27 @@ function throwError(action, src, type, cause) {
22948
23894
  });
22949
23895
  }
22950
23896
 
22951
- const BARE_ITEM = 'Bare Item';
22952
-
22953
- const BOOLEAN = 'Boolean';
22954
-
22955
- const BYTES = 'Byte Sequence';
22956
-
22957
- const DECIMAL = 'Decimal';
22958
-
22959
- const INTEGER = 'Integer';
22960
-
22961
- function isInvalidInt(value) {
22962
- return value < -999999999999999 || 999999999999999 < value;
23897
+ function serializeError(src, type, cause) {
23898
+ return throwError('serialize', src, type, cause);
22963
23899
  }
22964
23900
 
22965
- const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
22966
-
22967
- const TOKEN = 'Token';
23901
+ /**
23902
+ * A class to represent structured field tokens when `Symbol` is not available.
23903
+ *
23904
+ * @group Structured Field
23905
+ *
23906
+ * @beta
23907
+ */
23908
+ class SfToken {
23909
+ constructor(description) {
23910
+ this.description = void 0;
23911
+ this.description = description;
23912
+ }
23913
+ }
22968
23914
 
22969
- const KEY = 'Key';
23915
+ const BARE_ITEM = 'Bare Item';
22970
23916
 
22971
- function serializeError(src, type, cause) {
22972
- return throwError('serialize', src, type, cause);
22973
- }
23917
+ const BOOLEAN = 'Boolean';
22974
23918
 
22975
23919
  // 4.1.9. Serializing a Boolean
22976
23920
  //
@@ -23009,6 +23953,8 @@ function base64encode(binary) {
23009
23953
  return btoa(String.fromCharCode(...binary));
23010
23954
  }
23011
23955
 
23956
+ const BYTES = 'Byte Sequence';
23957
+
23012
23958
  // 4.1.8. Serializing a Byte Sequence
23013
23959
  //
23014
23960
  // Given a Byte Sequence as input_bytes, return an ASCII string suitable
@@ -23040,6 +23986,12 @@ function serializeByteSequence(value) {
23040
23986
  return `:${base64encode(value)}:`;
23041
23987
  }
23042
23988
 
23989
+ const INTEGER = 'Integer';
23990
+
23991
+ function isInvalidInt(value) {
23992
+ return value < -999999999999999 || 999999999999999 < value;
23993
+ }
23994
+
23043
23995
  // 4.1.4. Serializing an Integer
23044
23996
  //
23045
23997
  // Given an Integer as input_integer, return an ASCII string suitable
@@ -23105,6 +24057,8 @@ function roundToEven(value, precision) {
23105
24057
  }
23106
24058
  }
23107
24059
 
24060
+ const DECIMAL = 'Decimal';
24061
+
23108
24062
  // 4.1.5. Serializing a Decimal
23109
24063
  //
23110
24064
  // Given a decimal number as input_decimal, return an ASCII string
@@ -23150,6 +24104,8 @@ function serializeDecimal(value) {
23150
24104
 
23151
24105
  const STRING = 'String';
23152
24106
 
24107
+ const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
24108
+
23153
24109
  // 4.1.6. Serializing a String
23154
24110
  //
23155
24111
  // Given a String as input_string, return an ASCII string suitable for
@@ -23185,6 +24141,8 @@ function symbolToStr(symbol) {
23185
24141
  return symbol.description || symbol.toString().slice(7, -1);
23186
24142
  }
23187
24143
 
24144
+ const TOKEN = 'Token';
24145
+
23188
24146
  function serializeToken(token) {
23189
24147
  const value = symbolToStr(token);
23190
24148
  if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
@@ -23252,6 +24210,8 @@ function serializeBareItem(value) {
23252
24210
  }
23253
24211
  }
23254
24212
 
24213
+ const KEY = 'Key';
24214
+
23255
24215
  // 4.1.1.3. Serializing a Key
23256
24216
  //
23257
24217
  // Given a key as input_key, return an ASCII string suitable for use in
@@ -23493,36 +24453,6 @@ function urlToRelativePath(url, base) {
23493
24453
  return toPath.join('/');
23494
24454
  }
23495
24455
 
23496
- /**
23497
- * Generate a random v4 UUID
23498
- *
23499
- * @returns A random v4 UUID
23500
- *
23501
- * @group Utils
23502
- *
23503
- * @beta
23504
- */
23505
- function uuid() {
23506
- try {
23507
- return crypto.randomUUID();
23508
- } catch (error) {
23509
- try {
23510
- const url = URL.createObjectURL(new Blob());
23511
- const uuid = url.toString();
23512
- URL.revokeObjectURL(url);
23513
- return uuid.slice(uuid.lastIndexOf('/') + 1);
23514
- } catch (error) {
23515
- let dt = new Date().getTime();
23516
- const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
23517
- const r = (dt + Math.random() * 16) % 16 | 0;
23518
- dt = Math.floor(dt / 16);
23519
- return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
23520
- });
23521
- return uuid;
23522
- }
23523
- }
23524
- }
23525
-
23526
24456
  const toRounded = value => Math.round(value);
23527
24457
  const toUrlSafe = (value, options) => {
23528
24458
  if (options != null && options.baseUrl) {
@@ -23748,6 +24678,36 @@ function appendCmcdQuery(url, cmcd, options) {
23748
24678
  return `${url}${separator}${query}`;
23749
24679
  }
23750
24680
 
24681
+ /**
24682
+ * Generate a random v4 UUID
24683
+ *
24684
+ * @returns A random v4 UUID
24685
+ *
24686
+ * @group Utils
24687
+ *
24688
+ * @beta
24689
+ */
24690
+ function uuid() {
24691
+ try {
24692
+ return crypto.randomUUID();
24693
+ } catch (error) {
24694
+ try {
24695
+ const url = URL.createObjectURL(new Blob());
24696
+ const uuid = url.toString();
24697
+ URL.revokeObjectURL(url);
24698
+ return uuid.slice(uuid.lastIndexOf('/') + 1);
24699
+ } catch (error) {
24700
+ let dt = new Date().getTime();
24701
+ const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
24702
+ const r = (dt + Math.random() * 16) % 16 | 0;
24703
+ dt = Math.floor(dt / 16);
24704
+ return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
24705
+ });
24706
+ return uuid;
24707
+ }
24708
+ }
24709
+ }
24710
+
23751
24711
  /**
23752
24712
  * Controller to deal with Common Media Client Data (CMCD)
23753
24713
  * @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
@@ -23791,7 +24751,7 @@ class CMCDController {
23791
24751
  su: !this.initialized
23792
24752
  });
23793
24753
  } catch (error) {
23794
- logger.warn('Could not generate manifest CMCD data.', error);
24754
+ this.hls.logger.warn('Could not generate manifest CMCD data.', error);
23795
24755
  }
23796
24756
  };
23797
24757
  /**
@@ -23811,9 +24771,15 @@ class CMCDController {
23811
24771
  data.tb = this.getTopBandwidth(ot) / 1000;
23812
24772
  data.bl = this.getBufferLength(ot);
23813
24773
  }
24774
+ const next = this.getNextFrag(fragment);
24775
+ if (next) {
24776
+ if (next.url && next.url !== fragment.url) {
24777
+ data.nor = next.url;
24778
+ }
24779
+ }
23814
24780
  this.apply(context, data);
23815
24781
  } catch (error) {
23816
- logger.warn('Could not generate segment CMCD data.', error);
24782
+ this.hls.logger.warn('Could not generate segment CMCD data.', error);
23817
24783
  }
23818
24784
  };
23819
24785
  this.hls = hls;
@@ -23903,7 +24869,7 @@ class CMCDController {
23903
24869
  data.su = this.buffering;
23904
24870
  }
23905
24871
 
23906
- // TODO: Implement rtp, nrr, nor, dl
24872
+ // TODO: Implement rtp, nrr, dl
23907
24873
 
23908
24874
  const {
23909
24875
  includeKeys
@@ -23914,15 +24880,28 @@ class CMCDController {
23914
24880
  return acc;
23915
24881
  }, {});
23916
24882
  }
24883
+ const options = {
24884
+ baseUrl: context.url
24885
+ };
23917
24886
  if (this.useHeaders) {
23918
24887
  if (!context.headers) {
23919
24888
  context.headers = {};
23920
24889
  }
23921
- appendCmcdHeaders(context.headers, data);
24890
+ appendCmcdHeaders(context.headers, data, options);
23922
24891
  } else {
23923
- context.url = appendCmcdQuery(context.url, data);
24892
+ context.url = appendCmcdQuery(context.url, data, options);
23924
24893
  }
23925
24894
  }
24895
+ getNextFrag(fragment) {
24896
+ var _this$hls$levels$frag;
24897
+ const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
24898
+ if (levelDetails) {
24899
+ const index = fragment.sn - levelDetails.startSN;
24900
+ return levelDetails.fragments[index + 1];
24901
+ }
24902
+ return undefined;
24903
+ }
24904
+
23926
24905
  /**
23927
24906
  * The CMCD object type.
23928
24907
  */
@@ -24051,10 +25030,10 @@ class CMCDController {
24051
25030
  }
24052
25031
 
24053
25032
  const PATHWAY_PENALTY_DURATION_MS = 300000;
24054
- class ContentSteeringController {
25033
+ class ContentSteeringController extends Logger {
24055
25034
  constructor(hls) {
25035
+ super('content-steering', hls.logger);
24056
25036
  this.hls = void 0;
24057
- this.log = void 0;
24058
25037
  this.loader = null;
24059
25038
  this.uri = null;
24060
25039
  this.pathwayId = '.';
@@ -24069,7 +25048,6 @@ class ContentSteeringController {
24069
25048
  this.subtitleTracks = null;
24070
25049
  this.penalizedPathways = {};
24071
25050
  this.hls = hls;
24072
- this.log = logger.log.bind(logger, `[content-steering]:`);
24073
25051
  this.registerListeners();
24074
25052
  }
24075
25053
  registerListeners() {
@@ -24193,7 +25171,7 @@ class ContentSteeringController {
24193
25171
  errorAction.resolved = this.pathwayId !== errorPathway;
24194
25172
  }
24195
25173
  if (!errorAction.resolved) {
24196
- 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)}`);
25174
+ 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)}`);
24197
25175
  }
24198
25176
  }
24199
25177
  }
@@ -24364,7 +25342,7 @@ class ContentSteeringController {
24364
25342
  onSuccess: (response, stats, context, networkDetails) => {
24365
25343
  this.log(`Loaded steering manifest: "${url}"`);
24366
25344
  const steeringData = response.data;
24367
- if (steeringData.VERSION !== 1) {
25345
+ if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
24368
25346
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
24369
25347
  return;
24370
25348
  }
@@ -25334,7 +26312,7 @@ function timelineConfig() {
25334
26312
  /**
25335
26313
  * @ignore
25336
26314
  */
25337
- function mergeConfig(defaultConfig, userConfig) {
26315
+ function mergeConfig(defaultConfig, userConfig, logger) {
25338
26316
  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
25339
26317
  throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
25340
26318
  }
@@ -25404,7 +26382,7 @@ function deepCpy(obj) {
25404
26382
  /**
25405
26383
  * @ignore
25406
26384
  */
25407
- function enableStreamingMode(config) {
26385
+ function enableStreamingMode(config, logger) {
25408
26386
  const currentLoader = config.loader;
25409
26387
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
25410
26388
  // If a developer has configured their own loader, respect that choice
@@ -25421,10 +26399,9 @@ function enableStreamingMode(config) {
25421
26399
  }
25422
26400
  }
25423
26401
 
25424
- let chromeOrFirefox;
25425
26402
  class LevelController extends BasePlaylistController {
25426
26403
  constructor(hls, contentSteeringController) {
25427
- super(hls, '[level-controller]');
26404
+ super(hls, 'level-controller');
25428
26405
  this._levels = [];
25429
26406
  this._firstLevel = -1;
25430
26407
  this._maxAutoLevel = -1;
@@ -25495,23 +26472,15 @@ class LevelController extends BasePlaylistController {
25495
26472
  let videoCodecFound = false;
25496
26473
  let audioCodecFound = false;
25497
26474
  data.levels.forEach(levelParsed => {
25498
- var _audioCodec, _videoCodec;
26475
+ var _videoCodec;
25499
26476
  const attributes = levelParsed.attrs;
25500
-
25501
- // erase audio codec info if browser does not support mp4a.40.34.
25502
- // demuxer will autodetect codec and fallback to mpeg/audio
25503
26477
  let {
25504
26478
  audioCodec,
25505
26479
  videoCodec
25506
26480
  } = levelParsed;
25507
- if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
25508
- chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
25509
- if (chromeOrFirefox) {
25510
- levelParsed.audioCodec = audioCodec = undefined;
25511
- }
25512
- }
25513
26481
  if (audioCodec) {
25514
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
26482
+ // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
26483
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
25515
26484
  }
25516
26485
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
25517
26486
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -25853,7 +26822,12 @@ class LevelController extends BasePlaylistController {
25853
26822
  if (curLevel.fragmentError === 0) {
25854
26823
  curLevel.loadError = 0;
25855
26824
  }
25856
- this.playlistLoaded(level, data, curLevel.details);
26825
+ // Ignore matching details populated by loading a Media Playlist directly
26826
+ let previousDetails = curLevel.details;
26827
+ if (previousDetails === data.details && previousDetails.advanced) {
26828
+ previousDetails = undefined;
26829
+ }
26830
+ this.playlistLoaded(level, data, previousDetails);
25857
26831
  } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
25858
26832
  // received a delta playlist update that cannot be merged
25859
26833
  details.deltaUpdateFailed = true;
@@ -26097,6 +27071,8 @@ class KeyLoader {
26097
27071
  }
26098
27072
  return this.loadKeyEME(keyInfo, frag);
26099
27073
  case 'AES-128':
27074
+ case 'AES-256':
27075
+ case 'AES-256-CTR':
26100
27076
  return this.loadKeyHTTP(keyInfo, frag);
26101
27077
  default:
26102
27078
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -26234,8 +27210,9 @@ const STALL_MINIMUM_DURATION_MS = 250;
26234
27210
  const MAX_START_GAP_JUMP = 2.0;
26235
27211
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
26236
27212
  const SKIP_BUFFER_RANGE_START = 0.05;
26237
- class GapController {
27213
+ class GapController extends Logger {
26238
27214
  constructor(config, media, fragmentTracker, hls) {
27215
+ super('gap-controller', hls.logger);
26239
27216
  this.config = void 0;
26240
27217
  this.media = null;
26241
27218
  this.fragmentTracker = void 0;
@@ -26245,6 +27222,7 @@ class GapController {
26245
27222
  this.stalled = null;
26246
27223
  this.moved = false;
26247
27224
  this.seeking = false;
27225
+ this.ended = 0;
26248
27226
  this.config = config;
26249
27227
  this.media = media;
26250
27228
  this.fragmentTracker = fragmentTracker;
@@ -26262,7 +27240,7 @@ class GapController {
26262
27240
  *
26263
27241
  * @param lastCurrentTime - Previously read playhead position
26264
27242
  */
26265
- poll(lastCurrentTime, activeFrag) {
27243
+ poll(lastCurrentTime, activeFrag, levelDetails, state) {
26266
27244
  const {
26267
27245
  config,
26268
27246
  media,
@@ -26281,6 +27259,7 @@ class GapController {
26281
27259
 
26282
27260
  // The playhead is moving, no-op
26283
27261
  if (currentTime !== lastCurrentTime) {
27262
+ this.ended = 0;
26284
27263
  this.moved = true;
26285
27264
  if (!seeking) {
26286
27265
  this.nudgeRetry = 0;
@@ -26289,7 +27268,7 @@ class GapController {
26289
27268
  // The playhead is now moving, but was previously stalled
26290
27269
  if (this.stallReported) {
26291
27270
  const _stalledDuration = self.performance.now() - stalled;
26292
- logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
27271
+ this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
26293
27272
  this.stallReported = false;
26294
27273
  }
26295
27274
  this.stalled = null;
@@ -26325,7 +27304,6 @@ class GapController {
26325
27304
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
26326
27305
  // The addition poll gives the browser a chance to jump the gap for us
26327
27306
  if (!this.moved && this.stalled !== null) {
26328
- var _level$details;
26329
27307
  // There is no playable buffer (seeked, waiting for buffer)
26330
27308
  const isBuffered = bufferInfo.len > 0;
26331
27309
  if (!isBuffered && !nextStart) {
@@ -26337,9 +27315,8 @@ class GapController {
26337
27315
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
26338
27316
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
26339
27317
  // that begins over 1 target duration after the video start position.
26340
- const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
26341
- const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
26342
- const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
27318
+ const isLive = !!(levelDetails != null && levelDetails.live);
27319
+ const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
26343
27320
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
26344
27321
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
26345
27322
  if (!media.paused) {
@@ -26357,6 +27334,17 @@ class GapController {
26357
27334
  }
26358
27335
  const stalledDuration = tnow - stalled;
26359
27336
  if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
27337
+ // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
27338
+ if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
27339
+ if (stalledDuration < 1000 || this.ended) {
27340
+ return;
27341
+ }
27342
+ this.ended = currentTime;
27343
+ this.hls.trigger(Events.MEDIA_ENDED, {
27344
+ stalled: true
27345
+ });
27346
+ return;
27347
+ }
26360
27348
  // Report stalling after trying to fix
26361
27349
  this._reportStall(bufferInfo);
26362
27350
  if (!this.media) {
@@ -26400,7 +27388,7 @@ class GapController {
26400
27388
  // needs to cross some sort of threshold covering all source-buffers content
26401
27389
  // to start playing properly.
26402
27390
  if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
26403
- logger.warn('Trying to nudge playhead over buffer-hole');
27391
+ this.warn('Trying to nudge playhead over buffer-hole');
26404
27392
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
26405
27393
  // We only try to jump the hole if it's under the configured size
26406
27394
  // Reset stalled so to rearm watchdog timer
@@ -26424,7 +27412,7 @@ class GapController {
26424
27412
  // Report stalled error once
26425
27413
  this.stallReported = true;
26426
27414
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
26427
- logger.warn(error.message);
27415
+ this.warn(error.message);
26428
27416
  hls.trigger(Events.ERROR, {
26429
27417
  type: ErrorTypes.MEDIA_ERROR,
26430
27418
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -26492,7 +27480,7 @@ class GapController {
26492
27480
  }
26493
27481
  }
26494
27482
  const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
26495
- logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
27483
+ this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
26496
27484
  this.moved = true;
26497
27485
  this.stalled = null;
26498
27486
  media.currentTime = targetTime;
@@ -26533,7 +27521,7 @@ class GapController {
26533
27521
  const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
26534
27522
  // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
26535
27523
  const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
26536
- logger.warn(error.message);
27524
+ this.warn(error.message);
26537
27525
  media.currentTime = targetTime;
26538
27526
  hls.trigger(Events.ERROR, {
26539
27527
  type: ErrorTypes.MEDIA_ERROR,
@@ -26543,7 +27531,7 @@ class GapController {
26543
27531
  });
26544
27532
  } else {
26545
27533
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
26546
- logger.error(error.message);
27534
+ this.error(error.message);
26547
27535
  hls.trigger(Events.ERROR, {
26548
27536
  type: ErrorTypes.MEDIA_ERROR,
26549
27537
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -26558,7 +27546,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
26558
27546
 
26559
27547
  class StreamController extends BaseStreamController {
26560
27548
  constructor(hls, fragmentTracker, keyLoader) {
26561
- super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
27549
+ super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
26562
27550
  this.audioCodecSwap = false;
26563
27551
  this.gapController = null;
26564
27552
  this.level = -1;
@@ -26566,27 +27554,43 @@ class StreamController extends BaseStreamController {
26566
27554
  this.altAudio = false;
26567
27555
  this.audioOnly = false;
26568
27556
  this.fragPlaying = null;
26569
- this.onvplaying = null;
26570
- this.onvseeked = null;
26571
27557
  this.fragLastKbps = 0;
26572
27558
  this.couldBacktrack = false;
26573
27559
  this.backtrackFragment = null;
26574
27560
  this.audioCodecSwitch = false;
26575
27561
  this.videoBuffer = null;
26576
- this._registerListeners();
27562
+ this.onMediaPlaying = () => {
27563
+ // tick to speed up FRAG_CHANGED triggering
27564
+ this.tick();
27565
+ };
27566
+ this.onMediaSeeked = () => {
27567
+ const media = this.media;
27568
+ const currentTime = media ? media.currentTime : null;
27569
+ if (isFiniteNumber(currentTime)) {
27570
+ this.log(`Media seeked to ${currentTime.toFixed(3)}`);
27571
+ }
27572
+
27573
+ // If seeked was issued before buffer was appended do not tick immediately
27574
+ const bufferInfo = this.getMainFwdBufferInfo();
27575
+ if (bufferInfo === null || bufferInfo.len === 0) {
27576
+ this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
27577
+ return;
27578
+ }
27579
+
27580
+ // tick to speed up FRAG_CHANGED triggering
27581
+ this.tick();
27582
+ };
27583
+ this.registerListeners();
26577
27584
  }
26578
- _registerListeners() {
27585
+ registerListeners() {
27586
+ super.registerListeners();
26579
27587
  const {
26580
27588
  hls
26581
27589
  } = this;
26582
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
26583
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
26584
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
26585
27590
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
26586
27591
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
26587
27592
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
26588
27593
  hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26589
- hls.on(Events.ERROR, this.onError, this);
26590
27594
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
26591
27595
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
26592
27596
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -26594,17 +27598,14 @@ class StreamController extends BaseStreamController {
26594
27598
  hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
26595
27599
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
26596
27600
  }
26597
- _unregisterListeners() {
27601
+ unregisterListeners() {
27602
+ super.unregisterListeners();
26598
27603
  const {
26599
27604
  hls
26600
27605
  } = this;
26601
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
26602
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
26603
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
26604
27606
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
26605
27607
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
26606
27608
  hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26607
- hls.off(Events.ERROR, this.onError, this);
26608
27609
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
26609
27610
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
26610
27611
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -26613,7 +27614,9 @@ class StreamController extends BaseStreamController {
26613
27614
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
26614
27615
  }
26615
27616
  onHandlerDestroying() {
26616
- this._unregisterListeners();
27617
+ // @ts-ignore
27618
+ this.onMediaPlaying = this.onMediaSeeked = null;
27619
+ this.unregisterListeners();
26617
27620
  super.onHandlerDestroying();
26618
27621
  }
26619
27622
  startLoad(startPosition) {
@@ -26711,6 +27714,9 @@ class StreamController extends BaseStreamController {
26711
27714
  this.checkFragmentChanged();
26712
27715
  }
26713
27716
  doTickIdle() {
27717
+ if (!this.buffering) {
27718
+ return;
27719
+ }
26714
27720
  const {
26715
27721
  hls,
26716
27722
  levelLastLoaded,
@@ -26938,20 +27944,17 @@ class StreamController extends BaseStreamController {
26938
27944
  onMediaAttached(event, data) {
26939
27945
  super.onMediaAttached(event, data);
26940
27946
  const media = data.media;
26941
- this.onvplaying = this.onMediaPlaying.bind(this);
26942
- this.onvseeked = this.onMediaSeeked.bind(this);
26943
- media.addEventListener('playing', this.onvplaying);
26944
- media.addEventListener('seeked', this.onvseeked);
27947
+ media.addEventListener('playing', this.onMediaPlaying);
27948
+ media.addEventListener('seeked', this.onMediaSeeked);
26945
27949
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
26946
27950
  }
26947
27951
  onMediaDetaching() {
26948
27952
  const {
26949
27953
  media
26950
27954
  } = this;
26951
- if (media && this.onvplaying && this.onvseeked) {
26952
- media.removeEventListener('playing', this.onvplaying);
26953
- media.removeEventListener('seeked', this.onvseeked);
26954
- this.onvplaying = this.onvseeked = null;
27955
+ if (media) {
27956
+ media.removeEventListener('playing', this.onMediaPlaying);
27957
+ media.removeEventListener('seeked', this.onMediaSeeked);
26955
27958
  this.videoBuffer = null;
26956
27959
  }
26957
27960
  this.fragPlaying = null;
@@ -26961,27 +27964,6 @@ class StreamController extends BaseStreamController {
26961
27964
  }
26962
27965
  super.onMediaDetaching();
26963
27966
  }
26964
- onMediaPlaying() {
26965
- // tick to speed up FRAG_CHANGED triggering
26966
- this.tick();
26967
- }
26968
- onMediaSeeked() {
26969
- const media = this.media;
26970
- const currentTime = media ? media.currentTime : null;
26971
- if (isFiniteNumber(currentTime)) {
26972
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
26973
- }
26974
-
26975
- // If seeked was issued before buffer was appended do not tick immediately
26976
- const bufferInfo = this.getMainFwdBufferInfo();
26977
- if (bufferInfo === null || bufferInfo.len === 0) {
26978
- this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
26979
- return;
26980
- }
26981
-
26982
- // tick to speed up FRAG_CHANGED triggering
26983
- this.tick();
26984
- }
26985
27967
  onManifestLoading() {
26986
27968
  // reset buffer on manifest loading
26987
27969
  this.log('Trigger BUFFER_RESET');
@@ -27273,8 +28255,10 @@ class StreamController extends BaseStreamController {
27273
28255
  }
27274
28256
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
27275
28257
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
27276
- const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
27277
- gapController.poll(this.lastCurrentTime, activeFrag);
28258
+ const state = this.state;
28259
+ const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
28260
+ const levelDetails = this.getLevelDetails();
28261
+ gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
27278
28262
  }
27279
28263
  this.lastCurrentTime = media.currentTime;
27280
28264
  }
@@ -27607,6 +28591,17 @@ class StreamController extends BaseStreamController {
27607
28591
  getMainFwdBufferInfo() {
27608
28592
  return this.getFwdBufferInfo(this.mediaBuffer ? this.mediaBuffer : this.media, PlaylistLevelType.MAIN);
27609
28593
  }
28594
+ get maxBufferLength() {
28595
+ const {
28596
+ levels,
28597
+ level
28598
+ } = this;
28599
+ const levelInfo = levels == null ? void 0 : levels[level];
28600
+ if (!levelInfo) {
28601
+ return this.config.maxBufferLength;
28602
+ }
28603
+ return this.getMaxBufferLength(levelInfo.maxBitrate);
28604
+ }
27610
28605
  backtrack(frag) {
27611
28606
  this.couldBacktrack = true;
27612
28607
  // Causes findFragments to backtrack through fragments to find the keyframe
@@ -27712,7 +28707,7 @@ class Hls {
27712
28707
  * Get the video-dev/hls.js package version.
27713
28708
  */
27714
28709
  static get version() {
27715
- return "1.5.7";
28710
+ return "1.5.8-0.canary.10044";
27716
28711
  }
27717
28712
 
27718
28713
  /**
@@ -27775,9 +28770,12 @@ class Hls {
27775
28770
  * The configuration object provided on player instantiation.
27776
28771
  */
27777
28772
  this.userConfig = void 0;
28773
+ /**
28774
+ * The logger functions used by this player instance, configured on player instantiation.
28775
+ */
28776
+ this.logger = void 0;
27778
28777
  this.coreComponents = void 0;
27779
28778
  this.networkControllers = void 0;
27780
- this.started = false;
27781
28779
  this._emitter = new EventEmitter();
27782
28780
  this._autoLevelCapping = -1;
27783
28781
  this._maxHdcpLevel = null;
@@ -27794,11 +28792,11 @@ class Hls {
27794
28792
  this._media = null;
27795
28793
  this.url = null;
27796
28794
  this.triggeringException = void 0;
27797
- enableLogs(userConfig.debug || false, 'Hls instance');
27798
- const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
28795
+ const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
28796
+ const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
27799
28797
  this.userConfig = userConfig;
27800
28798
  if (config.progressive) {
27801
- enableStreamingMode(config);
28799
+ enableStreamingMode(config, logger);
27802
28800
  }
27803
28801
 
27804
28802
  // core controllers and network loaders
@@ -27811,7 +28809,9 @@ class Hls {
27811
28809
  } = config;
27812
28810
  const errorController = new ConfigErrorController(this);
27813
28811
  const abrController = this.abrController = new ConfigAbrController(this);
27814
- const bufferController = this.bufferController = new ConfigBufferController(this);
28812
+ // FragmentTracker must be defined before StreamController because the order of event handling is important
28813
+ const fragmentTracker = new FragmentTracker(this);
28814
+ const bufferController = this.bufferController = new ConfigBufferController(this, fragmentTracker);
27815
28815
  const capLevelController = this.capLevelController = new ConfigCapLevelController(this);
27816
28816
  const fpsController = new ConfigFpsController(this);
27817
28817
  const playListLoader = new PlaylistLoader(this);
@@ -27820,8 +28820,6 @@ class Hls {
27820
28820
  // ConentSteeringController is defined before LevelController to receive Multivariant Playlist events first
27821
28821
  const contentSteering = ConfigContentSteeringController ? new ConfigContentSteeringController(this) : null;
27822
28822
  const levelController = this.levelController = new LevelController(this, contentSteering);
27823
- // FragmentTracker must be defined before StreamController because the order of event handling is important
27824
- const fragmentTracker = new FragmentTracker(this);
27825
28823
  const keyLoader = new KeyLoader(this.config);
27826
28824
  const streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader);
27827
28825
 
@@ -27897,7 +28895,7 @@ class Hls {
27897
28895
  try {
27898
28896
  return this.emit(event, event, eventObject);
27899
28897
  } catch (error) {
27900
- logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
28898
+ this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
27901
28899
  // Prevent recursion in error event handlers that throw #5497
27902
28900
  if (!this.triggeringException) {
27903
28901
  this.triggeringException = true;
@@ -27923,7 +28921,7 @@ class Hls {
27923
28921
  * Dispose of the instance
27924
28922
  */
27925
28923
  destroy() {
27926
- logger.log('destroy');
28924
+ this.logger.log('destroy');
27927
28925
  this.trigger(Events.DESTROYING, undefined);
27928
28926
  this.detachMedia();
27929
28927
  this.removeAllListeners();
@@ -27944,7 +28942,7 @@ class Hls {
27944
28942
  * Attaches Hls.js to a media element
27945
28943
  */
27946
28944
  attachMedia(media) {
27947
- logger.log('attachMedia');
28945
+ this.logger.log('attachMedia');
27948
28946
  this._media = media;
27949
28947
  this.trigger(Events.MEDIA_ATTACHING, {
27950
28948
  media: media
@@ -27955,7 +28953,7 @@ class Hls {
27955
28953
  * Detach Hls.js from the media
27956
28954
  */
27957
28955
  detachMedia() {
27958
- logger.log('detachMedia');
28956
+ this.logger.log('detachMedia');
27959
28957
  this.trigger(Events.MEDIA_DETACHING, undefined);
27960
28958
  this._media = null;
27961
28959
  }
@@ -27972,7 +28970,7 @@ class Hls {
27972
28970
  });
27973
28971
  this._autoLevelCapping = -1;
27974
28972
  this._maxHdcpLevel = null;
27975
- logger.log(`loadSource:${loadingSource}`);
28973
+ this.logger.log(`loadSource:${loadingSource}`);
27976
28974
  if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
27977
28975
  this.detachMedia();
27978
28976
  this.attachMedia(media);
@@ -27991,8 +28989,7 @@ class Hls {
27991
28989
  * Defaults to -1 (None: starts from earliest point)
27992
28990
  */
27993
28991
  startLoad(startPosition = -1) {
27994
- logger.log(`startLoad(${startPosition})`);
27995
- this.started = true;
28992
+ this.logger.log(`startLoad(${startPosition})`);
27996
28993
  this.networkControllers.forEach(controller => {
27997
28994
  controller.startLoad(startPosition);
27998
28995
  });
@@ -28002,34 +28999,31 @@ class Hls {
28002
28999
  * Stop loading of any stream data.
28003
29000
  */
28004
29001
  stopLoad() {
28005
- logger.log('stopLoad');
28006
- this.started = false;
29002
+ this.logger.log('stopLoad');
28007
29003
  this.networkControllers.forEach(controller => {
28008
29004
  controller.stopLoad();
28009
29005
  });
28010
29006
  }
28011
29007
 
28012
29008
  /**
28013
- * Resumes stream controller segment loading if previously started.
29009
+ * Resumes stream controller segment loading after `pauseBuffering` has been called.
28014
29010
  */
28015
29011
  resumeBuffering() {
28016
- if (this.started) {
28017
- this.networkControllers.forEach(controller => {
28018
- if ('fragmentLoader' in controller) {
28019
- controller.startLoad(-1);
28020
- }
28021
- });
28022
- }
29012
+ this.networkControllers.forEach(controller => {
29013
+ if (controller.resumeBuffering) {
29014
+ controller.resumeBuffering();
29015
+ }
29016
+ });
28023
29017
  }
28024
29018
 
28025
29019
  /**
28026
- * Stops stream controller segment loading without changing 'started' state like stopLoad().
29020
+ * Prevents stream controller from loading new segments until `resumeBuffering` is called.
28027
29021
  * This allows for media buffering to be paused without interupting playlist loading.
28028
29022
  */
28029
29023
  pauseBuffering() {
28030
29024
  this.networkControllers.forEach(controller => {
28031
- if ('fragmentLoader' in controller) {
28032
- controller.stopLoad();
29025
+ if (controller.pauseBuffering) {
29026
+ controller.pauseBuffering();
28033
29027
  }
28034
29028
  });
28035
29029
  }
@@ -28038,7 +29032,7 @@ class Hls {
28038
29032
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
28039
29033
  */
28040
29034
  swapAudioCodec() {
28041
- logger.log('swapAudioCodec');
29035
+ this.logger.log('swapAudioCodec');
28042
29036
  this.streamController.swapAudioCodec();
28043
29037
  }
28044
29038
 
@@ -28049,7 +29043,7 @@ class Hls {
28049
29043
  * Automatic recovery of media-errors by this process is configurable.
28050
29044
  */
28051
29045
  recoverMediaError() {
28052
- logger.log('recoverMediaError');
29046
+ this.logger.log('recoverMediaError');
28053
29047
  const media = this._media;
28054
29048
  this.detachMedia();
28055
29049
  if (media) {
@@ -28079,7 +29073,7 @@ class Hls {
28079
29073
  * 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.
28080
29074
  */
28081
29075
  set currentLevel(newLevel) {
28082
- logger.log(`set currentLevel:${newLevel}`);
29076
+ this.logger.log(`set currentLevel:${newLevel}`);
28083
29077
  this.levelController.manualLevel = newLevel;
28084
29078
  this.streamController.immediateLevelSwitch();
28085
29079
  }
@@ -28098,7 +29092,7 @@ class Hls {
28098
29092
  * @param newLevel - Pass -1 for automatic level selection
28099
29093
  */
28100
29094
  set nextLevel(newLevel) {
28101
- logger.log(`set nextLevel:${newLevel}`);
29095
+ this.logger.log(`set nextLevel:${newLevel}`);
28102
29096
  this.levelController.manualLevel = newLevel;
28103
29097
  this.streamController.nextLevelSwitch();
28104
29098
  }
@@ -28117,7 +29111,7 @@ class Hls {
28117
29111
  * @param newLevel - Pass -1 for automatic level selection
28118
29112
  */
28119
29113
  set loadLevel(newLevel) {
28120
- logger.log(`set loadLevel:${newLevel}`);
29114
+ this.logger.log(`set loadLevel:${newLevel}`);
28121
29115
  this.levelController.manualLevel = newLevel;
28122
29116
  }
28123
29117
 
@@ -28148,7 +29142,7 @@ class Hls {
28148
29142
  * Sets "first-level", see getter.
28149
29143
  */
28150
29144
  set firstLevel(newLevel) {
28151
- logger.log(`set firstLevel:${newLevel}`);
29145
+ this.logger.log(`set firstLevel:${newLevel}`);
28152
29146
  this.levelController.firstLevel = newLevel;
28153
29147
  }
28154
29148
 
@@ -28173,7 +29167,7 @@ class Hls {
28173
29167
  * (determined from download of first segment)
28174
29168
  */
28175
29169
  set startLevel(newLevel) {
28176
- logger.log(`set startLevel:${newLevel}`);
29170
+ this.logger.log(`set startLevel:${newLevel}`);
28177
29171
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
28178
29172
  if (newLevel !== -1) {
28179
29173
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -28248,7 +29242,7 @@ class Hls {
28248
29242
  */
28249
29243
  set autoLevelCapping(newLevel) {
28250
29244
  if (this._autoLevelCapping !== newLevel) {
28251
- logger.log(`set autoLevelCapping:${newLevel}`);
29245
+ this.logger.log(`set autoLevelCapping:${newLevel}`);
28252
29246
  this._autoLevelCapping = newLevel;
28253
29247
  this.levelController.checkMaxAutoUpdated();
28254
29248
  }
@@ -28353,6 +29347,9 @@ class Hls {
28353
29347
  get mainForwardBufferInfo() {
28354
29348
  return this.streamController.getMainFwdBufferInfo();
28355
29349
  }
29350
+ get maxBufferLength() {
29351
+ return this.streamController.maxBufferLength;
29352
+ }
28356
29353
 
28357
29354
  /**
28358
29355
  * Find and select the best matching audio track, making a level switch when a Group change is necessary.