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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +1 -0
  2. package/dist/hls-demo.js +10 -0
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +1930 -1095
  5. package/dist/hls.js.d.ts +63 -50
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1609 -778
  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 +1363 -542
  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 +1635 -815
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +18 -18
  20. package/src/config.ts +3 -2
  21. package/src/controller/abr-controller.ts +21 -20
  22. package/src/controller/audio-stream-controller.ts +15 -16
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +7 -7
  25. package/src/controller/base-stream-controller.ts +56 -29
  26. package/src/controller/buffer-controller.ts +11 -11
  27. package/src/controller/cap-level-controller.ts +1 -2
  28. package/src/controller/cmcd-controller.ts +25 -3
  29. package/src/controller/content-steering-controller.ts +8 -6
  30. package/src/controller/eme-controller.ts +9 -22
  31. package/src/controller/error-controller.ts +6 -8
  32. package/src/controller/fps-controller.ts +2 -3
  33. package/src/controller/gap-controller.ts +43 -16
  34. package/src/controller/latency-controller.ts +9 -11
  35. package/src/controller/level-controller.ts +5 -17
  36. package/src/controller/stream-controller.ts +25 -32
  37. package/src/controller/subtitle-stream-controller.ts +13 -14
  38. package/src/controller/subtitle-track-controller.ts +5 -3
  39. package/src/controller/timeline-controller.ts +23 -30
  40. package/src/crypt/aes-crypto.ts +21 -2
  41. package/src/crypt/decrypter-aes-mode.ts +4 -0
  42. package/src/crypt/decrypter.ts +32 -18
  43. package/src/crypt/fast-aes-key.ts +24 -5
  44. package/src/demux/audio/adts.ts +9 -4
  45. package/src/demux/sample-aes.ts +2 -0
  46. package/src/demux/transmuxer-interface.ts +4 -12
  47. package/src/demux/transmuxer-worker.ts +4 -4
  48. package/src/demux/transmuxer.ts +16 -3
  49. package/src/demux/tsdemuxer.ts +63 -37
  50. package/src/demux/video/avc-video-parser.ts +208 -119
  51. package/src/demux/video/base-video-parser.ts +134 -2
  52. package/src/demux/video/exp-golomb.ts +0 -208
  53. package/src/demux/video/hevc-video-parser.ts +746 -0
  54. package/src/events.ts +7 -0
  55. package/src/hls.ts +42 -34
  56. package/src/loader/fragment-loader.ts +9 -2
  57. package/src/loader/key-loader.ts +2 -0
  58. package/src/loader/level-key.ts +10 -9
  59. package/src/remux/mp4-generator.ts +196 -1
  60. package/src/remux/mp4-remuxer.ts +23 -7
  61. package/src/task-loop.ts +5 -2
  62. package/src/types/component-api.ts +2 -0
  63. package/src/types/demuxer.ts +3 -0
  64. package/src/types/events.ts +4 -0
  65. package/src/utils/codecs.ts +33 -4
  66. package/src/utils/encryption-methods-util.ts +21 -0
  67. package/src/utils/logger.ts +53 -24
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,6 +370,23 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
369
370
  return ErrorDetails;
370
371
  }({});
371
372
 
373
+ class Logger {
374
+ constructor(label, logger) {
375
+ this.trace = void 0;
376
+ this.debug = void 0;
377
+ this.log = void 0;
378
+ this.warn = void 0;
379
+ this.info = void 0;
380
+ this.error = void 0;
381
+ const lb = `[${label}]:`;
382
+ this.trace = noop;
383
+ this.debug = logger.debug.bind(null, lb);
384
+ this.log = logger.log.bind(null, lb);
385
+ this.warn = logger.warn.bind(null, lb);
386
+ this.info = logger.info.bind(null, lb);
387
+ this.error = logger.error.bind(null, lb);
388
+ }
389
+ }
372
390
  const noop = function noop() {};
373
391
  const fakeLogger = {
374
392
  trace: noop,
@@ -378,7 +396,9 @@ const fakeLogger = {
378
396
  info: noop,
379
397
  error: noop
380
398
  };
381
- let exportedLogger = fakeLogger;
399
+ function createLogger() {
400
+ return _extends({}, fakeLogger);
401
+ }
382
402
 
383
403
  // let lastCallTime;
384
404
  // function formatMsgWithTimeInfo(type, msg) {
@@ -389,35 +409,36 @@ let exportedLogger = fakeLogger;
389
409
  // return msg;
390
410
  // }
391
411
 
392
- function consolePrintFn(type) {
412
+ function consolePrintFn(type, id) {
393
413
  const func = self.console[type];
394
- if (func) {
395
- return func.bind(self.console, `[${type}] >`);
396
- }
397
- return noop;
414
+ return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
398
415
  }
399
- function exportLoggerFunctions(debugConfig, ...functions) {
400
- functions.forEach(function (type) {
401
- exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
402
- });
416
+ function getLoggerFn(key, debugConfig, id) {
417
+ return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
403
418
  }
404
- function enableLogs(debugConfig, id) {
419
+ let exportedLogger = createLogger();
420
+ function enableLogs(debugConfig, context, id) {
405
421
  // check that console is available
422
+ const newLogger = createLogger();
406
423
  if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
407
- exportLoggerFunctions(debugConfig,
424
+ const keys = [
408
425
  // Remove out from list here to hard-disable a log-level
409
426
  // 'trace',
410
- 'debug', 'log', 'info', 'warn', 'error');
427
+ 'debug', 'log', 'info', 'warn', 'error'];
428
+ keys.forEach(key => {
429
+ newLogger[key] = getLoggerFn(key, debugConfig, id);
430
+ });
411
431
  // Some browsers don't allow to use bind on console object anyway
412
432
  // fallback to default if needed
413
433
  try {
414
- exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.4"}`);
434
+ newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.5-0.canary.9977"}`);
415
435
  } catch (e) {
416
- exportedLogger = fakeLogger;
436
+ /* log fn threw an exception. All logger methods are no-ops. */
437
+ return createLogger();
417
438
  }
418
- } else {
419
- exportedLogger = fakeLogger;
420
439
  }
440
+ exportedLogger = newLogger;
441
+ return newLogger;
421
442
  }
422
443
  const logger = exportedLogger;
423
444
 
@@ -1036,6 +1057,26 @@ function strToUtf8array(str) {
1036
1057
  return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
1037
1058
  }
1038
1059
 
1060
+ var DecrypterAesMode = {
1061
+ cbc: 0,
1062
+ ctr: 1
1063
+ };
1064
+
1065
+ function isFullSegmentEncryption(method) {
1066
+ return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
1067
+ }
1068
+ function getAesModeFromFullSegmentMethod(method) {
1069
+ switch (method) {
1070
+ case 'AES-128':
1071
+ case 'AES-256':
1072
+ return DecrypterAesMode.cbc;
1073
+ case 'AES-256-CTR':
1074
+ return DecrypterAesMode.ctr;
1075
+ default:
1076
+ throw new Error(`invalid full segment method ${method}`);
1077
+ }
1078
+ }
1079
+
1039
1080
  /** returns `undefined` is `self` is missing, e.g. in node */
1040
1081
  const optionalSelf = typeof self !== 'undefined' ? self : undefined;
1041
1082
 
@@ -2686,12 +2727,12 @@ class LevelKey {
2686
2727
  this.keyFormatVersions = formatversions;
2687
2728
  this.iv = iv;
2688
2729
  this.encrypted = method ? method !== 'NONE' : false;
2689
- this.isCommonEncryption = this.encrypted && method !== 'AES-128';
2730
+ this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
2690
2731
  }
2691
2732
  isSupported() {
2692
2733
  // If it's Segment encryption or No encryption, just select that key system
2693
2734
  if (this.method) {
2694
- if (this.method === 'AES-128' || this.method === 'NONE') {
2735
+ if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
2695
2736
  return true;
2696
2737
  }
2697
2738
  if (this.keyFormat === 'identity') {
@@ -2713,14 +2754,13 @@ class LevelKey {
2713
2754
  if (!this.encrypted || !this.uri) {
2714
2755
  return null;
2715
2756
  }
2716
- if (this.method === 'AES-128' && this.uri && !this.iv) {
2757
+ if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
2717
2758
  if (typeof sn !== 'number') {
2718
2759
  // We are fetching decryption data for a initialization segment
2719
- // If the segment was encrypted with AES-128
2760
+ // If the segment was encrypted with AES-128/256
2720
2761
  // It must have an IV defined. We cannot substitute the Segment Number in.
2721
- if (this.method === 'AES-128' && !this.iv) {
2722
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2723
- }
2762
+ logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2763
+
2724
2764
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
2725
2765
  sn = 0;
2726
2766
  }
@@ -2999,23 +3039,28 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
2999
3039
  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
3000
3040
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
3001
3041
  }
3002
-
3003
- // Idealy fLaC and Opus would be first (spec-compliant) but
3004
- // some browsers will report that fLaC is supported then fail.
3005
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3006
3042
  const codecsToCheck = {
3043
+ // Idealy fLaC and Opus would be first (spec-compliant) but
3044
+ // some browsers will report that fLaC is supported then fail.
3045
+ // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3007
3046
  flac: ['flac', 'fLaC', 'FLAC'],
3008
- opus: ['opus', 'Opus']
3047
+ opus: ['opus', 'Opus'],
3048
+ // Replace audio codec info if browser does not support mp4a.40.34,
3049
+ // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
3050
+ 'mp4a.40.34': ['mp3']
3009
3051
  }[lowerCaseCodec];
3010
3052
  for (let i = 0; i < codecsToCheck.length; i++) {
3053
+ var _getMediaSource;
3011
3054
  if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
3012
3055
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
3013
3056
  return codecsToCheck[i];
3057
+ } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
3058
+ return '';
3014
3059
  }
3015
3060
  }
3016
3061
  return lowerCaseCodec;
3017
3062
  }
3018
- const AUDIO_CODEC_REGEXP = /flac|opus/i;
3063
+ const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
3019
3064
  function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
3020
3065
  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
3021
3066
  }
@@ -3038,6 +3083,16 @@ function convertAVC1ToAVCOTI(codec) {
3038
3083
  }
3039
3084
  return codec;
3040
3085
  }
3086
+ function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
3087
+ const MediaSource = getMediaSource(preferManagedMediaSource) || {
3088
+ isTypeSupported: () => false
3089
+ };
3090
+ return {
3091
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
3092
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
3093
+ ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
3094
+ };
3095
+ }
3041
3096
 
3042
3097
  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;
3043
3098
  const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -4704,7 +4759,47 @@ class LatencyController {
4704
4759
  this.currentTime = 0;
4705
4760
  this.stallCount = 0;
4706
4761
  this._latency = null;
4707
- this.timeupdateHandler = () => this.timeupdate();
4762
+ this.onTimeupdate = () => {
4763
+ const {
4764
+ media,
4765
+ levelDetails
4766
+ } = this;
4767
+ if (!media || !levelDetails) {
4768
+ return;
4769
+ }
4770
+ this.currentTime = media.currentTime;
4771
+ const latency = this.computeLatency();
4772
+ if (latency === null) {
4773
+ return;
4774
+ }
4775
+ this._latency = latency;
4776
+
4777
+ // Adapt playbackRate to meet target latency in low-latency mode
4778
+ const {
4779
+ lowLatencyMode,
4780
+ maxLiveSyncPlaybackRate
4781
+ } = this.config;
4782
+ if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4783
+ return;
4784
+ }
4785
+ const targetLatency = this.targetLatency;
4786
+ if (targetLatency === null) {
4787
+ return;
4788
+ }
4789
+ const distanceFromTarget = latency - targetLatency;
4790
+ // Only adjust playbackRate when within one target duration of targetLatency
4791
+ // and more than one second from under-buffering.
4792
+ // Playback further than one target duration from target can be considered DVR playback.
4793
+ const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4794
+ const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4795
+ if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4796
+ const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4797
+ const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4798
+ media.playbackRate = Math.min(max, Math.max(1, rate));
4799
+ } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4800
+ media.playbackRate = 1;
4801
+ }
4802
+ };
4708
4803
  this.hls = hls;
4709
4804
  this.config = hls.config;
4710
4805
  this.registerListeners();
@@ -4796,7 +4891,7 @@ class LatencyController {
4796
4891
  this.onMediaDetaching();
4797
4892
  this.levelDetails = null;
4798
4893
  // @ts-ignore
4799
- this.hls = this.timeupdateHandler = null;
4894
+ this.hls = null;
4800
4895
  }
4801
4896
  registerListeners() {
4802
4897
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -4814,11 +4909,11 @@ class LatencyController {
4814
4909
  }
4815
4910
  onMediaAttached(event, data) {
4816
4911
  this.media = data.media;
4817
- this.media.addEventListener('timeupdate', this.timeupdateHandler);
4912
+ this.media.addEventListener('timeupdate', this.onTimeupdate);
4818
4913
  }
4819
4914
  onMediaDetaching() {
4820
4915
  if (this.media) {
4821
- this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4916
+ this.media.removeEventListener('timeupdate', this.onTimeupdate);
4822
4917
  this.media = null;
4823
4918
  }
4824
4919
  }
@@ -4832,10 +4927,10 @@ class LatencyController {
4832
4927
  }) {
4833
4928
  this.levelDetails = details;
4834
4929
  if (details.advanced) {
4835
- this.timeupdate();
4930
+ this.onTimeupdate();
4836
4931
  }
4837
4932
  if (!details.live && this.media) {
4838
- this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4933
+ this.media.removeEventListener('timeupdate', this.onTimeupdate);
4839
4934
  }
4840
4935
  }
4841
4936
  onError(event, data) {
@@ -4845,48 +4940,7 @@ class LatencyController {
4845
4940
  }
4846
4941
  this.stallCount++;
4847
4942
  if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
4848
- logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
4849
- }
4850
- }
4851
- timeupdate() {
4852
- const {
4853
- media,
4854
- levelDetails
4855
- } = this;
4856
- if (!media || !levelDetails) {
4857
- return;
4858
- }
4859
- this.currentTime = media.currentTime;
4860
- const latency = this.computeLatency();
4861
- if (latency === null) {
4862
- return;
4863
- }
4864
- this._latency = latency;
4865
-
4866
- // Adapt playbackRate to meet target latency in low-latency mode
4867
- const {
4868
- lowLatencyMode,
4869
- maxLiveSyncPlaybackRate
4870
- } = this.config;
4871
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4872
- return;
4873
- }
4874
- const targetLatency = this.targetLatency;
4875
- if (targetLatency === null) {
4876
- return;
4877
- }
4878
- const distanceFromTarget = latency - targetLatency;
4879
- // Only adjust playbackRate when within one target duration of targetLatency
4880
- // and more than one second from under-buffering.
4881
- // Playback further than one target duration from target can be considered DVR playback.
4882
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4883
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4884
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4885
- const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4886
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4887
- media.playbackRate = Math.min(max, Math.max(1, rate));
4888
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4889
- media.playbackRate = 1;
4943
+ this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
4890
4944
  }
4891
4945
  }
4892
4946
  estimateLiveEdge() {
@@ -5658,18 +5712,13 @@ var ErrorActionFlags = {
5658
5712
  MoveAllAlternatesMatchingHDCP: 2,
5659
5713
  SwitchToSDR: 4
5660
5714
  }; // Reserved for future use
5661
- class ErrorController {
5715
+ class ErrorController extends Logger {
5662
5716
  constructor(hls) {
5717
+ super('error-controller', hls.logger);
5663
5718
  this.hls = void 0;
5664
5719
  this.playlistError = 0;
5665
5720
  this.penalizedRenditions = {};
5666
- this.log = void 0;
5667
- this.warn = void 0;
5668
- this.error = void 0;
5669
5721
  this.hls = hls;
5670
- this.log = logger.log.bind(logger, `[info]:`);
5671
- this.warn = logger.warn.bind(logger, `[warning]:`);
5672
- this.error = logger.error.bind(logger, `[error]:`);
5673
5722
  this.registerListeners();
5674
5723
  }
5675
5724
  registerListeners() {
@@ -6021,16 +6070,13 @@ class ErrorController {
6021
6070
  }
6022
6071
  }
6023
6072
 
6024
- class BasePlaylistController {
6073
+ class BasePlaylistController extends Logger {
6025
6074
  constructor(hls, logPrefix) {
6075
+ super(logPrefix, hls.logger);
6026
6076
  this.hls = void 0;
6027
6077
  this.timer = -1;
6028
6078
  this.requestScheduled = -1;
6029
6079
  this.canLoad = false;
6030
- this.log = void 0;
6031
- this.warn = void 0;
6032
- this.log = logger.log.bind(logger, `${logPrefix}:`);
6033
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
6034
6080
  this.hls = hls;
6035
6081
  }
6036
6082
  destroy() {
@@ -6063,7 +6109,7 @@ class BasePlaylistController {
6063
6109
  try {
6064
6110
  uri = new self.URL(attr.URI, previous.url).href;
6065
6111
  } catch (error) {
6066
- logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
6112
+ this.warn(`Could not construct new URL for Rendition Report: ${error}`);
6067
6113
  uri = attr.URI || '';
6068
6114
  }
6069
6115
  // Use exact match. Otherwise, the last partial match, if any, will be used
@@ -6822,8 +6868,9 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
6822
6868
  return -1;
6823
6869
  }
6824
6870
 
6825
- class AbrController {
6871
+ class AbrController extends Logger {
6826
6872
  constructor(_hls) {
6873
+ super('abr', _hls.logger);
6827
6874
  this.hls = void 0;
6828
6875
  this.lastLevelLoadSec = 0;
6829
6876
  this.lastLoadedFragLevel = -1;
@@ -6937,7 +6984,7 @@ class AbrController {
6937
6984
  this.resetEstimator(nextLoadLevelBitrate);
6938
6985
  }
6939
6986
  this.clearTimer();
6940
- logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6987
+ this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6941
6988
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
6942
6989
  Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
6943
6990
  Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
@@ -6957,7 +7004,7 @@ class AbrController {
6957
7004
  }
6958
7005
  resetEstimator(abrEwmaDefaultEstimate) {
6959
7006
  if (abrEwmaDefaultEstimate) {
6960
- logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
7007
+ this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6961
7008
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
6962
7009
  }
6963
7010
  this.firstSelection = -1;
@@ -7189,7 +7236,7 @@ class AbrController {
7189
7236
  }
7190
7237
  const firstLevel = this.hls.firstLevel;
7191
7238
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
7192
- logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7239
+ this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7193
7240
  return clamped;
7194
7241
  }
7195
7242
  get forcedAutoLevel() {
@@ -7274,13 +7321,13 @@ class AbrController {
7274
7321
  // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
7275
7322
  const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
7276
7323
  maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
7277
- logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7324
+ this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7278
7325
  // don't use conservative factor on bitrate test
7279
7326
  bwFactor = bwUpFactor = 1;
7280
7327
  }
7281
7328
  }
7282
7329
  const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
7283
- logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7330
+ this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7284
7331
  if (bestLevel > -1) {
7285
7332
  return bestLevel;
7286
7333
  }
@@ -7342,7 +7389,7 @@ class AbrController {
7342
7389
  currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
7343
7390
  currentFrameRate = minFramerate;
7344
7391
  currentBw = Math.max(currentBw, minBitrate);
7345
- logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
7392
+ this.log(`picked start tier ${JSON.stringify(startTier)}`);
7346
7393
  } else {
7347
7394
  currentCodecSet = level == null ? void 0 : level.codecSet;
7348
7395
  currentVideoRange = level == null ? void 0 : level.videoRange;
@@ -7366,11 +7413,11 @@ class AbrController {
7366
7413
  const levels = this.hls.levels;
7367
7414
  const index = levels.indexOf(levelInfo);
7368
7415
  if (decodingInfo.error) {
7369
- logger.warn(`[abr] MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7416
+ this.warn(`MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7370
7417
  } else if (!decodingInfo.supported) {
7371
- logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7418
+ this.warn(`Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7372
7419
  if (index > -1 && levels.length > 1) {
7373
- logger.log(`[abr] Removing unsupported level ${index}`);
7420
+ this.log(`Removing unsupported level ${index}`);
7374
7421
  this.hls.removeLevel(index);
7375
7422
  }
7376
7423
  }
@@ -7417,9 +7464,9 @@ class AbrController {
7417
7464
  const forcedAutoLevel = this.forcedAutoLevel;
7418
7465
  if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
7419
7466
  if (levelsSkipped.length) {
7420
- 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}`);
7467
+ 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}`);
7421
7468
  }
7422
- 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}`);
7469
+ 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}`);
7423
7470
  }
7424
7471
  if (firstSelection) {
7425
7472
  this.firstSelection = i;
@@ -7473,8 +7520,9 @@ class AbrController {
7473
7520
  * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
7474
7521
  * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
7475
7522
  */
7476
- class TaskLoop {
7477
- constructor() {
7523
+ class TaskLoop extends Logger {
7524
+ constructor(label, logger) {
7525
+ super(label, logger);
7478
7526
  this._boundTick = void 0;
7479
7527
  this._tickTimer = null;
7480
7528
  this._tickInterval = null;
@@ -8565,8 +8613,8 @@ function createLoaderContext(frag, part = null) {
8565
8613
  var _frag$decryptdata;
8566
8614
  let byteRangeStart = start;
8567
8615
  let byteRangeEnd = end;
8568
- if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
8569
- // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
8616
+ if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
8617
+ // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
8570
8618
  // has the unencrypted size specified in the range.
8571
8619
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
8572
8620
  const fragmentLen = end - start;
@@ -8599,6 +8647,9 @@ function createGapLoadError(frag, part) {
8599
8647
  (part ? part : frag).stats.aborted = true;
8600
8648
  return new LoadError(errorData);
8601
8649
  }
8650
+ function isMethodFullSegmentAesCbc(method) {
8651
+ return method === 'AES-128' || method === 'AES-256';
8652
+ }
8602
8653
  class LoadError extends Error {
8603
8654
  constructor(data) {
8604
8655
  super(data.error.message);
@@ -8608,33 +8659,61 @@ class LoadError extends Error {
8608
8659
  }
8609
8660
 
8610
8661
  class AESCrypto {
8611
- constructor(subtle, iv) {
8662
+ constructor(subtle, iv, aesMode) {
8612
8663
  this.subtle = void 0;
8613
8664
  this.aesIV = void 0;
8665
+ this.aesMode = void 0;
8614
8666
  this.subtle = subtle;
8615
8667
  this.aesIV = iv;
8668
+ this.aesMode = aesMode;
8616
8669
  }
8617
8670
  decrypt(data, key) {
8618
- return this.subtle.decrypt({
8619
- name: 'AES-CBC',
8620
- iv: this.aesIV
8621
- }, key, data);
8671
+ switch (this.aesMode) {
8672
+ case DecrypterAesMode.cbc:
8673
+ return this.subtle.decrypt({
8674
+ name: 'AES-CBC',
8675
+ iv: this.aesIV
8676
+ }, key, data);
8677
+ case DecrypterAesMode.ctr:
8678
+ return this.subtle.decrypt({
8679
+ name: 'AES-CTR',
8680
+ counter: this.aesIV,
8681
+ length: 64
8682
+ },
8683
+ //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
8684
+ key, data);
8685
+ default:
8686
+ throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
8687
+ }
8622
8688
  }
8623
8689
  }
8624
8690
 
8625
8691
  class FastAESKey {
8626
- constructor(subtle, key) {
8692
+ constructor(subtle, key, aesMode) {
8627
8693
  this.subtle = void 0;
8628
8694
  this.key = void 0;
8695
+ this.aesMode = void 0;
8629
8696
  this.subtle = subtle;
8630
8697
  this.key = key;
8698
+ this.aesMode = aesMode;
8631
8699
  }
8632
8700
  expandKey() {
8701
+ const subtleAlgoName = getSubtleAlgoName(this.aesMode);
8633
8702
  return this.subtle.importKey('raw', this.key, {
8634
- name: 'AES-CBC'
8703
+ name: subtleAlgoName
8635
8704
  }, false, ['encrypt', 'decrypt']);
8636
8705
  }
8637
8706
  }
8707
+ function getSubtleAlgoName(aesMode) {
8708
+ switch (aesMode) {
8709
+ case DecrypterAesMode.cbc:
8710
+ return 'AES-CBC';
8711
+ case DecrypterAesMode.ctr:
8712
+ return 'AES-CTR';
8713
+ default:
8714
+ throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
8715
+ }
8716
+ }
8638
8717
 
8639
8718
  // PKCS7
8640
8719
  function removePadding(array) {
@@ -8884,7 +8963,8 @@ class Decrypter {
8884
8963
  this.currentIV = null;
8885
8964
  this.currentResult = null;
8886
8965
  this.useSoftware = void 0;
8887
- this.useSoftware = config.enableSoftwareAES;
8966
+ this.enableSoftwareAES = void 0;
8967
+ this.enableSoftwareAES = config.enableSoftwareAES;
8888
8968
  this.removePKCS7Padding = removePKCS7Padding;
8889
8969
  // built in decryptor expects PKCS7 padding
8890
8970
  if (removePKCS7Padding) {
@@ -8897,9 +8977,7 @@ class Decrypter {
8897
8977
  /* no-op */
8898
8978
  }
8899
8979
  }
8900
- if (this.subtle === null) {
8901
- this.useSoftware = true;
8902
- }
8980
+ this.useSoftware = this.subtle === null;
8903
8981
  }
8904
8982
  destroy() {
8905
8983
  this.subtle = null;
@@ -8937,10 +9015,10 @@ class Decrypter {
8937
9015
  this.softwareDecrypter = null;
8938
9016
  }
8939
9017
  }
8940
- decrypt(data, key, iv) {
9018
+ decrypt(data, key, iv, aesMode) {
8941
9019
  if (this.useSoftware) {
8942
9020
  return new Promise((resolve, reject) => {
8943
- this.softwareDecrypt(new Uint8Array(data), key, iv);
9021
+ this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
8944
9022
  const decryptResult = this.flush();
8945
9023
  if (decryptResult) {
8946
9024
  resolve(decryptResult.buffer);
@@ -8949,17 +9027,21 @@ class Decrypter {
8949
9027
  }
8950
9028
  });
8951
9029
  }
8952
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
9030
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
8953
9031
  }
8954
9032
 
8955
9033
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
8956
9034
  // data is handled in the flush() call
8957
- softwareDecrypt(data, key, iv) {
9035
+ softwareDecrypt(data, key, iv, aesMode) {
8958
9036
  const {
8959
9037
  currentIV,
8960
9038
  currentResult,
8961
9039
  remainderData
8962
9040
  } = this;
9041
+ if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
9042
+ logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
9043
+ return null;
9044
+ }
8963
9045
  this.logOnce('JS AES decrypt');
8964
9046
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
8965
9047
  // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
@@ -8992,11 +9074,11 @@ class Decrypter {
8992
9074
  }
8993
9075
  return result;
8994
9076
  }
8995
- webCryptoDecrypt(data, key, iv) {
9077
+ webCryptoDecrypt(data, key, iv, aesMode) {
8996
9078
  const subtle = this.subtle;
8997
9079
  if (this.key !== key || !this.fastAesKey) {
8998
9080
  this.key = key;
8999
- this.fastAesKey = new FastAESKey(subtle, key);
9081
+ this.fastAesKey = new FastAESKey(subtle, key, aesMode);
9000
9082
  }
9001
9083
  return this.fastAesKey.expandKey().then(aesKey => {
9002
9084
  // decrypt using web crypto
@@ -9004,22 +9086,25 @@ class Decrypter {
9004
9086
  return Promise.reject(new Error('web crypto not initialized'));
9005
9087
  }
9006
9088
  this.logOnce('WebCrypto AES decrypt');
9007
- const crypto = new AESCrypto(subtle, new Uint8Array(iv));
9089
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
9008
9090
  return crypto.decrypt(data.buffer, aesKey);
9009
9091
  }).catch(err => {
9010
9092
  logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
9011
- return this.onWebCryptoError(data, key, iv);
9093
+ return this.onWebCryptoError(data, key, iv, aesMode);
9012
9094
  });
9013
9095
  }
9014
- onWebCryptoError(data, key, iv) {
9015
- this.useSoftware = true;
9016
- this.logEnabled = true;
9017
- this.softwareDecrypt(data, key, iv);
9018
- const decryptResult = this.flush();
9019
- if (decryptResult) {
9020
- return decryptResult.buffer;
9096
+ onWebCryptoError(data, key, iv, aesMode) {
9097
+ const enableSoftwareAES = this.enableSoftwareAES;
9098
+ if (enableSoftwareAES) {
9099
+ this.useSoftware = true;
9100
+ this.logEnabled = true;
9101
+ this.softwareDecrypt(data, key, iv, aesMode);
9102
+ const decryptResult = this.flush();
9103
+ if (decryptResult) {
9104
+ return decryptResult.buffer;
9105
+ }
9021
9106
  }
9022
- throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
9107
+ throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
9023
9108
  }
9024
9109
  getValidChunk(data) {
9025
9110
  let currentChunk = data;
@@ -9070,7 +9155,7 @@ const State = {
9070
9155
  };
9071
9156
  class BaseStreamController extends TaskLoop {
9072
9157
  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
9073
- super();
9158
+ super(logPrefix, hls.logger);
9074
9159
  this.hls = void 0;
9075
9160
  this.fragPrevious = null;
9076
9161
  this.fragCurrent = null;
@@ -9095,22 +9180,89 @@ class BaseStreamController extends TaskLoop {
9095
9180
  this.startFragRequested = false;
9096
9181
  this.decrypter = void 0;
9097
9182
  this.initPTS = [];
9098
- this.onvseeking = null;
9099
- this.onvended = null;
9100
- this.logPrefix = '';
9101
- this.log = void 0;
9102
- this.warn = void 0;
9183
+ this.buffering = true;
9184
+ this.onMediaSeeking = () => {
9185
+ const {
9186
+ config,
9187
+ fragCurrent,
9188
+ media,
9189
+ mediaBuffer,
9190
+ state
9191
+ } = this;
9192
+ const currentTime = media ? media.currentTime : 0;
9193
+ const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9194
+ this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9195
+ if (this.state === State.ENDED) {
9196
+ this.resetLoadingState();
9197
+ } else if (fragCurrent) {
9198
+ // Seeking while frag load is in progress
9199
+ const tolerance = config.maxFragLookUpTolerance;
9200
+ const fragStartOffset = fragCurrent.start - tolerance;
9201
+ const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9202
+ // if seeking out of buffered range or into new one
9203
+ if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9204
+ const pastFragment = currentTime > fragEndOffset;
9205
+ // if the seek position is outside the current fragment range
9206
+ if (currentTime < fragStartOffset || pastFragment) {
9207
+ if (pastFragment && fragCurrent.loader) {
9208
+ this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9209
+ fragCurrent.abortRequests();
9210
+ this.resetLoadingState();
9211
+ }
9212
+ this.fragPrevious = null;
9213
+ }
9214
+ }
9215
+ }
9216
+ if (media) {
9217
+ // Remove gap fragments
9218
+ this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9219
+ this.lastCurrentTime = currentTime;
9220
+ }
9221
+
9222
+ // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9223
+ if (!this.loadedmetadata && !bufferInfo.len) {
9224
+ this.nextLoadPosition = this.startPosition = currentTime;
9225
+ }
9226
+
9227
+ // Async tick to speed up processing
9228
+ this.tickImmediate();
9229
+ };
9230
+ this.onMediaEnded = () => {
9231
+ // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9232
+ this.startPosition = this.lastCurrentTime = 0;
9233
+ if (this.playlistType === PlaylistLevelType.MAIN) {
9234
+ this.hls.trigger(Events.MEDIA_ENDED, {
9235
+ stalled: false
9236
+ });
9237
+ }
9238
+ };
9103
9239
  this.playlistType = playlistType;
9104
- this.logPrefix = logPrefix;
9105
- this.log = logger.log.bind(logger, `${logPrefix}:`);
9106
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
9107
9240
  this.hls = hls;
9108
9241
  this.fragmentLoader = new FragmentLoader(hls.config);
9109
9242
  this.keyLoader = keyLoader;
9110
9243
  this.fragmentTracker = fragmentTracker;
9111
9244
  this.config = hls.config;
9112
9245
  this.decrypter = new Decrypter(hls.config);
9246
+ }
9247
+ registerListeners() {
9248
+ const {
9249
+ hls
9250
+ } = this;
9251
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
9252
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
9253
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
9113
9254
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9255
+ hls.on(Events.ERROR, this.onError, this);
9256
+ }
9257
+ unregisterListeners() {
9258
+ const {
9259
+ hls
9260
+ } = this;
9261
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
9262
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
9263
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
9264
+ hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9265
+ hls.off(Events.ERROR, this.onError, this);
9114
9266
  }
9115
9267
  doTick() {
9116
9268
  this.onTickEnd();
@@ -9134,6 +9286,12 @@ class BaseStreamController extends TaskLoop {
9134
9286
  this.clearNextTick();
9135
9287
  this.state = State.STOPPED;
9136
9288
  }
9289
+ pauseBuffering() {
9290
+ this.buffering = false;
9291
+ }
9292
+ resumeBuffering() {
9293
+ this.buffering = true;
9294
+ }
9137
9295
  _streamEnded(bufferInfo, levelDetails) {
9138
9296
  // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
9139
9297
  // of nothing loading/loaded return false
@@ -9164,10 +9322,8 @@ class BaseStreamController extends TaskLoop {
9164
9322
  }
9165
9323
  onMediaAttached(event, data) {
9166
9324
  const media = this.media = this.mediaBuffer = data.media;
9167
- this.onvseeking = this.onMediaSeeking.bind(this);
9168
- this.onvended = this.onMediaEnded.bind(this);
9169
- media.addEventListener('seeking', this.onvseeking);
9170
- media.addEventListener('ended', this.onvended);
9325
+ media.addEventListener('seeking', this.onMediaSeeking);
9326
+ media.addEventListener('ended', this.onMediaEnded);
9171
9327
  const config = this.config;
9172
9328
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
9173
9329
  this.startLoad(config.startPosition);
@@ -9181,10 +9337,9 @@ class BaseStreamController extends TaskLoop {
9181
9337
  }
9182
9338
 
9183
9339
  // remove video listeners
9184
- if (media && this.onvseeking && this.onvended) {
9185
- media.removeEventListener('seeking', this.onvseeking);
9186
- media.removeEventListener('ended', this.onvended);
9187
- this.onvseeking = this.onvended = null;
9340
+ if (media) {
9341
+ media.removeEventListener('seeking', this.onMediaSeeking);
9342
+ media.removeEventListener('ended', this.onMediaEnded);
9188
9343
  }
9189
9344
  if (this.keyLoader) {
9190
9345
  this.keyLoader.detach();
@@ -9194,56 +9349,8 @@ class BaseStreamController extends TaskLoop {
9194
9349
  this.fragmentTracker.removeAllFragments();
9195
9350
  this.stopLoad();
9196
9351
  }
9197
- onMediaSeeking() {
9198
- const {
9199
- config,
9200
- fragCurrent,
9201
- media,
9202
- mediaBuffer,
9203
- state
9204
- } = this;
9205
- const currentTime = media ? media.currentTime : 0;
9206
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9207
- this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9208
- if (this.state === State.ENDED) {
9209
- this.resetLoadingState();
9210
- } else if (fragCurrent) {
9211
- // Seeking while frag load is in progress
9212
- const tolerance = config.maxFragLookUpTolerance;
9213
- const fragStartOffset = fragCurrent.start - tolerance;
9214
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9215
- // if seeking out of buffered range or into new one
9216
- if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9217
- const pastFragment = currentTime > fragEndOffset;
9218
- // if the seek position is outside the current fragment range
9219
- if (currentTime < fragStartOffset || pastFragment) {
9220
- if (pastFragment && fragCurrent.loader) {
9221
- this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9222
- fragCurrent.abortRequests();
9223
- this.resetLoadingState();
9224
- }
9225
- this.fragPrevious = null;
9226
- }
9227
- }
9228
- }
9229
- if (media) {
9230
- // Remove gap fragments
9231
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9232
- this.lastCurrentTime = currentTime;
9233
- }
9234
-
9235
- // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9236
- if (!this.loadedmetadata && !bufferInfo.len) {
9237
- this.nextLoadPosition = this.startPosition = currentTime;
9238
- }
9239
-
9240
- // Async tick to speed up processing
9241
- this.tickImmediate();
9242
- }
9243
- onMediaEnded() {
9244
- // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9245
- this.startPosition = this.lastCurrentTime = 0;
9246
- }
9352
+ onManifestLoading() {}
9353
+ onError(event, data) {}
9247
9354
  onManifestLoaded(event, data) {
9248
9355
  this.startTimeOffset = data.startTimeOffset;
9249
9356
  this.initPTS = [];
@@ -9253,7 +9360,7 @@ class BaseStreamController extends TaskLoop {
9253
9360
  this.stopLoad();
9254
9361
  super.onHandlerDestroying();
9255
9362
  // @ts-ignore
9256
- this.hls = null;
9363
+ this.hls = this.onMediaSeeking = this.onMediaEnded = null;
9257
9364
  }
9258
9365
  onHandlerDestroyed() {
9259
9366
  this.state = State.STOPPED;
@@ -9384,10 +9491,10 @@ class BaseStreamController extends TaskLoop {
9384
9491
  const decryptData = frag.decryptdata;
9385
9492
 
9386
9493
  // check to see if the payload needs to be decrypted
9387
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
9494
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
9388
9495
  const startTime = self.performance.now();
9389
9496
  // decrypt init segment data
9390
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
9497
+ return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
9391
9498
  hls.trigger(Events.ERROR, {
9392
9499
  type: ErrorTypes.MEDIA_ERROR,
9393
9500
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -9499,7 +9606,7 @@ class BaseStreamController extends TaskLoop {
9499
9606
  }
9500
9607
  let keyLoadingPromise = null;
9501
9608
  if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
9502
- this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
9609
+ this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
9503
9610
  this.state = State.KEY_LOADING;
9504
9611
  this.fragCurrent = frag;
9505
9612
  keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
@@ -9530,7 +9637,7 @@ class BaseStreamController extends TaskLoop {
9530
9637
  const partIndex = this.getNextPart(partList, frag, targetBufferTime);
9531
9638
  if (partIndex > -1) {
9532
9639
  const part = partList[partIndex];
9533
- 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))}`);
9640
+ 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))}`);
9534
9641
  this.nextLoadPosition = part.start + part.duration;
9535
9642
  this.state = State.FRAG_LOADING;
9536
9643
  let _result;
@@ -9559,7 +9666,7 @@ class BaseStreamController extends TaskLoop {
9559
9666
  }
9560
9667
  }
9561
9668
  }
9562
- 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))}`);
9669
+ 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))}`);
9563
9670
  // Don't update nextLoadPosition for fragments which are not buffered
9564
9671
  if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
9565
9672
  this.nextLoadPosition = frag.start + frag.duration;
@@ -10144,7 +10251,7 @@ class BaseStreamController extends TaskLoop {
10144
10251
  errorAction.resolved = true;
10145
10252
  }
10146
10253
  } else {
10147
- logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10254
+ this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10148
10255
  return;
10149
10256
  }
10150
10257
  } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
@@ -10553,6 +10660,7 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
10553
10660
  */
10554
10661
  function getAudioConfig(observer, data, offset, audioCodec) {
10555
10662
  let adtsObjectType;
10663
+ let originalAdtsObjectType;
10556
10664
  let adtsExtensionSamplingIndex;
10557
10665
  let adtsChannelConfig;
10558
10666
  let config;
@@ -10560,7 +10668,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10560
10668
  const manifestCodec = audioCodec;
10561
10669
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
10562
10670
  // byte 2
10563
- adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10671
+ adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10564
10672
  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
10565
10673
  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
10566
10674
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
@@ -10577,8 +10685,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10577
10685
  // byte 3
10578
10686
  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
10579
10687
  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
10580
- // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
10581
- if (/firefox/i.test(userAgent)) {
10688
+ // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
10689
+ if (/firefox|palemoon/i.test(userAgent)) {
10582
10690
  if (adtsSamplingIndex >= 6) {
10583
10691
  adtsObjectType = 5;
10584
10692
  config = new Array(4);
@@ -10672,6 +10780,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10672
10780
  samplerate: adtsSamplingRates[adtsSamplingIndex],
10673
10781
  channelCount: adtsChannelConfig,
10674
10782
  codec: 'mp4a.40.' + adtsObjectType,
10783
+ parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
10675
10784
  manifestCodec
10676
10785
  };
10677
10786
  }
@@ -10726,7 +10835,8 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
10726
10835
  track.channelCount = config.channelCount;
10727
10836
  track.codec = config.codec;
10728
10837
  track.manifestCodec = config.manifestCodec;
10729
- logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10838
+ track.parsedCodec = config.parsedCodec;
10839
+ logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10730
10840
  }
10731
10841
  }
10732
10842
  function getFrameDuration(samplerate) {
@@ -11317,6 +11427,110 @@ class BaseVideoParser {
11317
11427
  logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
11318
11428
  }
11319
11429
  }
11430
+ parseNALu(track, array) {
11431
+ const len = array.byteLength;
11432
+ let state = track.naluState || 0;
11433
+ const lastState = state;
11434
+ const units = [];
11435
+ let i = 0;
11436
+ let value;
11437
+ let overflow;
11438
+ let unitType;
11439
+ let lastUnitStart = -1;
11440
+ let lastUnitType = 0;
11441
+ // logger.log('PES:' + Hex.hexDump(array));
11442
+
11443
+ if (state === -1) {
11444
+ // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
11445
+ lastUnitStart = 0;
11446
+ // NALu type is value read from offset 0
11447
+ lastUnitType = this.getNALuType(array, 0);
11448
+ state = 0;
11449
+ i = 1;
11450
+ }
11451
+ while (i < len) {
11452
+ value = array[i++];
11453
+ // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
11454
+ if (!state) {
11455
+ state = value ? 0 : 1;
11456
+ continue;
11457
+ }
11458
+ if (state === 1) {
11459
+ state = value ? 0 : 2;
11460
+ continue;
11461
+ }
11462
+ // here we have state either equal to 2 or 3
11463
+ if (!value) {
11464
+ state = 3;
11465
+ } else if (value === 1) {
11466
+ overflow = i - state - 1;
11467
+ if (lastUnitStart >= 0) {
11468
+ const unit = {
11469
+ data: array.subarray(lastUnitStart, overflow),
11470
+ type: lastUnitType
11471
+ };
11472
+ // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
11473
+ units.push(unit);
11474
+ } else {
11475
+ // lastUnitStart is undefined => this is the first start code found in this PES packet
11476
+ // first check if start code delimiter is overlapping between 2 PES packets,
11477
+ // ie it started in last packet (lastState not zero)
11478
+ // and ended at the beginning of this PES packet (i <= 4 - lastState)
11479
+ const lastUnit = this.getLastNalUnit(track.samples);
11480
+ if (lastUnit) {
11481
+ if (lastState && i <= 4 - lastState) {
11482
+ // start delimiter overlapping between PES packets
11483
+ // strip start delimiter bytes from the end of last NAL unit
11484
+ // check if lastUnit had a state different from zero
11485
+ if (lastUnit.state) {
11486
+ // strip last bytes
11487
+ lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
11488
+ }
11489
+ }
11490
+ // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
11491
+
11492
+ if (overflow > 0) {
11493
+ // logger.log('first NALU found with overflow:' + overflow);
11494
+ lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
11495
+ lastUnit.state = 0;
11496
+ }
11497
+ }
11498
+ }
11499
+ // check if we can read unit type
11500
+ if (i < len) {
11501
+ unitType = this.getNALuType(array, i);
11502
+ // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
11503
+ lastUnitStart = i;
11504
+ lastUnitType = unitType;
11505
+ state = 0;
11506
+ } else {
11507
+ // not enough byte to read unit type. let's read it on next PES parsing
11508
+ state = -1;
11509
+ }
11510
+ } else {
11511
+ state = 0;
11512
+ }
11513
+ }
11514
+ if (lastUnitStart >= 0 && state >= 0) {
11515
+ const unit = {
11516
+ data: array.subarray(lastUnitStart, len),
11517
+ type: lastUnitType,
11518
+ state: state
11519
+ };
11520
+ units.push(unit);
11521
+ // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
11522
+ }
11523
+ // no NALu found
11524
+ if (units.length === 0) {
11525
+ // append pes.data to previous NAL unit
11526
+ const lastUnit = this.getLastNalUnit(track.samples);
11527
+ if (lastUnit) {
11528
+ lastUnit.data = appendUint8Array(lastUnit.data, array);
11529
+ }
11530
+ }
11531
+ track.naluState = state;
11532
+ return units;
11533
+ }
11320
11534
  }
11321
11535
 
11322
11536
  /**
@@ -11459,21 +11673,171 @@ class ExpGolomb {
11459
11673
  readUInt() {
11460
11674
  return this.readBits(32);
11461
11675
  }
11676
+ }
11462
11677
 
11463
- /**
11464
- * Advance the ExpGolomb decoder past a scaling list. The scaling
11465
- * list is optionally transmitted as part of a sequence parameter
11466
- * set and is not relevant to transmuxing.
11467
- * @param count the number of entries in this scaling list
11678
+ class AvcVideoParser extends BaseVideoParser {
11679
+ parsePES(track, textTrack, pes, last, duration) {
11680
+ const units = this.parseNALu(track, pes.data);
11681
+ let VideoSample = this.VideoSample;
11682
+ let push;
11683
+ let spsfound = false;
11684
+ // free pes.data to save up some memory
11685
+ pes.data = null;
11686
+
11687
+ // if new NAL units found and last sample still there, let's push ...
11688
+ // this helps parsing streams with missing AUD (only do this if AUD never found)
11689
+ if (VideoSample && units.length && !track.audFound) {
11690
+ this.pushAccessUnit(VideoSample, track);
11691
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
11692
+ }
11693
+ units.forEach(unit => {
11694
+ var _VideoSample2;
11695
+ switch (unit.type) {
11696
+ // NDR
11697
+ case 1:
11698
+ {
11699
+ let iskey = false;
11700
+ push = true;
11701
+ const data = unit.data;
11702
+ // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
11703
+ if (spsfound && data.length > 4) {
11704
+ // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
11705
+ const sliceType = this.readSliceType(data);
11706
+ // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
11707
+ // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
11708
+ // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
11709
+ // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
11710
+ // if (sliceType === 2 || sliceType === 7) {
11711
+ if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
11712
+ iskey = true;
11713
+ }
11714
+ }
11715
+ if (iskey) {
11716
+ var _VideoSample;
11717
+ // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
11718
+ if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
11719
+ this.pushAccessUnit(VideoSample, track);
11720
+ VideoSample = this.VideoSample = null;
11721
+ }
11722
+ }
11723
+ if (!VideoSample) {
11724
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11725
+ }
11726
+ VideoSample.frame = true;
11727
+ VideoSample.key = iskey;
11728
+ break;
11729
+ // IDR
11730
+ }
11731
+ case 5:
11732
+ push = true;
11733
+ // handle PES not starting with AUD
11734
+ // if we have frame data already, that cannot belong to the same frame, so force a push
11735
+ if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
11736
+ this.pushAccessUnit(VideoSample, track);
11737
+ VideoSample = this.VideoSample = null;
11738
+ }
11739
+ if (!VideoSample) {
11740
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11741
+ }
11742
+ VideoSample.key = true;
11743
+ VideoSample.frame = true;
11744
+ break;
11745
+ // SEI
11746
+ case 6:
11747
+ {
11748
+ push = true;
11749
+ parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
11750
+ break;
11751
+ // SPS
11752
+ }
11753
+ case 7:
11754
+ {
11755
+ var _track$pixelRatio, _track$pixelRatio2;
11756
+ push = true;
11757
+ spsfound = true;
11758
+ const sps = unit.data;
11759
+ const config = this.readSPS(sps);
11760
+ 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]) {
11761
+ track.width = config.width;
11762
+ track.height = config.height;
11763
+ track.pixelRatio = config.pixelRatio;
11764
+ track.sps = [sps];
11765
+ track.duration = duration;
11766
+ const codecarray = sps.subarray(1, 4);
11767
+ let codecstring = 'avc1.';
11768
+ for (let i = 0; i < 3; i++) {
11769
+ let h = codecarray[i].toString(16);
11770
+ if (h.length < 2) {
11771
+ h = '0' + h;
11772
+ }
11773
+ codecstring += h;
11774
+ }
11775
+ track.codec = codecstring;
11776
+ }
11777
+ break;
11778
+ }
11779
+ // PPS
11780
+ case 8:
11781
+ push = true;
11782
+ track.pps = [unit.data];
11783
+ break;
11784
+ // AUD
11785
+ case 9:
11786
+ push = true;
11787
+ track.audFound = true;
11788
+ if (VideoSample) {
11789
+ this.pushAccessUnit(VideoSample, track);
11790
+ }
11791
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
11792
+ break;
11793
+ // Filler Data
11794
+ case 12:
11795
+ push = true;
11796
+ break;
11797
+ default:
11798
+ push = false;
11799
+ if (VideoSample) {
11800
+ VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
11801
+ }
11802
+ break;
11803
+ }
11804
+ if (VideoSample && push) {
11805
+ const units = VideoSample.units;
11806
+ units.push(unit);
11807
+ }
11808
+ });
11809
+ // if last PES packet, push samples
11810
+ if (last && VideoSample) {
11811
+ this.pushAccessUnit(VideoSample, track);
11812
+ this.VideoSample = null;
11813
+ }
11814
+ }
11815
+ getNALuType(data, offset) {
11816
+ return data[offset] & 0x1f;
11817
+ }
11818
+ readSliceType(data) {
11819
+ const eg = new ExpGolomb(data);
11820
+ // skip NALu type
11821
+ eg.readUByte();
11822
+ // discard first_mb_in_slice
11823
+ eg.readUEG();
11824
+ // return slice_type
11825
+ return eg.readUEG();
11826
+ }
11827
+
11828
+ /**
11829
+ * The scaling list is optionally transmitted as part of a sequence parameter
11830
+ * set and is not relevant to transmuxing.
11831
+ * @param count the number of entries in this scaling list
11468
11832
  * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
11469
11833
  */
11470
- skipScalingList(count) {
11834
+ skipScalingList(count, reader) {
11471
11835
  let lastScale = 8;
11472
11836
  let nextScale = 8;
11473
11837
  let deltaScale;
11474
11838
  for (let j = 0; j < count; j++) {
11475
11839
  if (nextScale !== 0) {
11476
- deltaScale = this.readEG();
11840
+ deltaScale = reader.readEG();
11477
11841
  nextScale = (lastScale + deltaScale + 256) % 256;
11478
11842
  }
11479
11843
  lastScale = nextScale === 0 ? lastScale : nextScale;
@@ -11488,7 +11852,8 @@ class ExpGolomb {
11488
11852
  * sequence parameter set, including the dimensions of the
11489
11853
  * associated video frames.
11490
11854
  */
11491
- readSPS() {
11855
+ readSPS(sps) {
11856
+ const eg = new ExpGolomb(sps);
11492
11857
  let frameCropLeftOffset = 0;
11493
11858
  let frameCropRightOffset = 0;
11494
11859
  let frameCropTopOffset = 0;
@@ -11496,13 +11861,13 @@ class ExpGolomb {
11496
11861
  let numRefFramesInPicOrderCntCycle;
11497
11862
  let scalingListCount;
11498
11863
  let i;
11499
- const readUByte = this.readUByte.bind(this);
11500
- const readBits = this.readBits.bind(this);
11501
- const readUEG = this.readUEG.bind(this);
11502
- const readBoolean = this.readBoolean.bind(this);
11503
- const skipBits = this.skipBits.bind(this);
11504
- const skipEG = this.skipEG.bind(this);
11505
- const skipUEG = this.skipUEG.bind(this);
11864
+ const readUByte = eg.readUByte.bind(eg);
11865
+ const readBits = eg.readBits.bind(eg);
11866
+ const readUEG = eg.readUEG.bind(eg);
11867
+ const readBoolean = eg.readBoolean.bind(eg);
11868
+ const skipBits = eg.skipBits.bind(eg);
11869
+ const skipEG = eg.skipEG.bind(eg);
11870
+ const skipUEG = eg.skipUEG.bind(eg);
11506
11871
  const skipScalingList = this.skipScalingList.bind(this);
11507
11872
  readUByte();
11508
11873
  const profileIdc = readUByte(); // profile_idc
@@ -11527,9 +11892,9 @@ class ExpGolomb {
11527
11892
  if (readBoolean()) {
11528
11893
  // seq_scaling_list_present_flag[ i ]
11529
11894
  if (i < 6) {
11530
- skipScalingList(16);
11895
+ skipScalingList(16, eg);
11531
11896
  } else {
11532
- skipScalingList(64);
11897
+ skipScalingList(64, eg);
11533
11898
  }
11534
11899
  }
11535
11900
  }
@@ -11634,19 +11999,15 @@ class ExpGolomb {
11634
11999
  pixelRatio: pixelRatio
11635
12000
  };
11636
12001
  }
11637
- readSliceType() {
11638
- // skip NALu type
11639
- this.readUByte();
11640
- // discard first_mb_in_slice
11641
- this.readUEG();
11642
- // return slice_type
11643
- return this.readUEG();
11644
- }
11645
12002
  }
11646
12003
 
11647
- class AvcVideoParser extends BaseVideoParser {
11648
- parseAVCPES(track, textTrack, pes, last, duration) {
11649
- const units = this.parseAVCNALu(track, pes.data);
12004
+ class HevcVideoParser extends BaseVideoParser {
12005
+ constructor(...args) {
12006
+ super(...args);
12007
+ this.initVPS = null;
12008
+ }
12009
+ parsePES(track, textTrack, pes, last, duration) {
12010
+ const units = this.parseNALu(track, pes.data);
11650
12011
  let VideoSample = this.VideoSample;
11651
12012
  let push;
11652
12013
  let spsfound = false;
@@ -11662,42 +12023,49 @@ class AvcVideoParser extends BaseVideoParser {
11662
12023
  units.forEach(unit => {
11663
12024
  var _VideoSample2;
11664
12025
  switch (unit.type) {
11665
- // NDR
12026
+ // NON-IDR, NON RANDOM ACCESS SLICE
12027
+ case 0:
11666
12028
  case 1:
11667
- {
11668
- let iskey = false;
11669
- push = true;
11670
- const data = unit.data;
11671
- // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
11672
- if (spsfound && data.length > 4) {
11673
- // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
11674
- const sliceType = new ExpGolomb(data).readSliceType();
11675
- // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
11676
- // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
11677
- // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
11678
- // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
11679
- // if (sliceType === 2 || sliceType === 7) {
11680
- if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
11681
- iskey = true;
11682
- }
11683
- }
11684
- if (iskey) {
11685
- var _VideoSample;
11686
- // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
11687
- if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
11688
- this.pushAccessUnit(VideoSample, track);
11689
- VideoSample = this.VideoSample = null;
11690
- }
11691
- }
11692
- if (!VideoSample) {
11693
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12029
+ case 2:
12030
+ case 3:
12031
+ case 4:
12032
+ case 5:
12033
+ case 6:
12034
+ case 7:
12035
+ case 8:
12036
+ case 9:
12037
+ if (!VideoSample) {
12038
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12039
+ }
12040
+ VideoSample.frame = true;
12041
+ push = true;
12042
+ break;
12043
+
12044
+ // CRA, BLA (random access picture)
12045
+ case 16:
12046
+ case 17:
12047
+ case 18:
12048
+ case 21:
12049
+ push = true;
12050
+ if (spsfound) {
12051
+ var _VideoSample;
12052
+ // handle PES not starting with AUD
12053
+ // if we have frame data already, that cannot belong to the same frame, so force a push
12054
+ if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
12055
+ this.pushAccessUnit(VideoSample, track);
12056
+ VideoSample = this.VideoSample = null;
11694
12057
  }
11695
- VideoSample.frame = true;
11696
- VideoSample.key = iskey;
11697
- break;
11698
- // IDR
11699
12058
  }
11700
- case 5:
12059
+ if (!VideoSample) {
12060
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12061
+ }
12062
+ VideoSample.key = true;
12063
+ VideoSample.frame = true;
12064
+ break;
12065
+
12066
+ // IDR
12067
+ case 19:
12068
+ case 20:
11701
12069
  push = true;
11702
12070
  // handle PES not starting with AUD
11703
12071
  // if we have frame data already, that cannot belong to the same frame, so force a push
@@ -11711,180 +12079,518 @@ class AvcVideoParser extends BaseVideoParser {
11711
12079
  VideoSample.key = true;
11712
12080
  VideoSample.frame = true;
11713
12081
  break;
12082
+
11714
12083
  // SEI
11715
- case 6:
11716
- {
11717
- push = true;
11718
- parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
11719
- break;
11720
- // SPS
11721
- }
11722
- case 7:
11723
- {
11724
- var _track$pixelRatio, _track$pixelRatio2;
11725
- push = true;
11726
- spsfound = true;
11727
- const sps = unit.data;
11728
- const expGolombDecoder = new ExpGolomb(sps);
11729
- const config = expGolombDecoder.readSPS();
11730
- 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]) {
11731
- track.width = config.width;
11732
- track.height = config.height;
11733
- track.pixelRatio = config.pixelRatio;
11734
- track.sps = [sps];
11735
- track.duration = duration;
11736
- const codecarray = sps.subarray(1, 4);
11737
- let codecstring = 'avc1.';
11738
- for (let i = 0; i < 3; i++) {
11739
- let h = codecarray[i].toString(16);
11740
- if (h.length < 2) {
11741
- h = '0' + h;
11742
- }
11743
- codecstring += h;
11744
- }
11745
- track.codec = codecstring;
11746
- }
11747
- break;
11748
- }
11749
- // PPS
11750
- case 8:
11751
- push = true;
11752
- track.pps = [unit.data];
11753
- break;
11754
- // AUD
11755
- case 9:
12084
+ case 39:
11756
12085
  push = true;
11757
- track.audFound = true;
11758
- if (VideoSample) {
11759
- this.pushAccessUnit(VideoSample, track);
11760
- }
11761
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12086
+ parseSEIMessageFromNALu(unit.data, 2,
12087
+ // NALu header size
12088
+ pes.pts, textTrack.samples);
11762
12089
  break;
11763
- // Filler Data
11764
- case 12:
12090
+
12091
+ // VPS
12092
+ case 32:
11765
12093
  push = true;
11766
- break;
11767
- default:
11768
- push = false;
11769
- if (VideoSample) {
11770
- VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
12094
+ if (!track.vps) {
12095
+ const config = this.readVPS(unit.data);
12096
+ track.params = _objectSpread2({}, config);
12097
+ this.initVPS = unit.data;
11771
12098
  }
12099
+ track.vps = [unit.data];
11772
12100
  break;
11773
- }
11774
- if (VideoSample && push) {
11775
- const units = VideoSample.units;
11776
- units.push(unit);
11777
- }
11778
- });
11779
- // if last PES packet, push samples
11780
- if (last && VideoSample) {
11781
- this.pushAccessUnit(VideoSample, track);
11782
- this.VideoSample = null;
11783
- }
11784
- }
11785
- parseAVCNALu(track, array) {
11786
- const len = array.byteLength;
11787
- let state = track.naluState || 0;
11788
- const lastState = state;
11789
- const units = [];
11790
- let i = 0;
11791
- let value;
11792
- let overflow;
11793
- let unitType;
11794
- let lastUnitStart = -1;
11795
- let lastUnitType = 0;
11796
- // logger.log('PES:' + Hex.hexDump(array));
11797
-
11798
- if (state === -1) {
11799
- // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
11800
- lastUnitStart = 0;
11801
- // NALu type is value read from offset 0
11802
- lastUnitType = array[0] & 0x1f;
11803
- state = 0;
11804
- i = 1;
11805
- }
11806
- while (i < len) {
11807
- value = array[i++];
11808
- // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
11809
- if (!state) {
11810
- state = value ? 0 : 1;
11811
- continue;
11812
- }
11813
- if (state === 1) {
11814
- state = value ? 0 : 2;
11815
- continue;
11816
- }
11817
- // here we have state either equal to 2 or 3
11818
- if (!value) {
11819
- state = 3;
11820
- } else if (value === 1) {
11821
- overflow = i - state - 1;
11822
- if (lastUnitStart >= 0) {
11823
- const unit = {
11824
- data: array.subarray(lastUnitStart, overflow),
11825
- type: lastUnitType
11826
- };
11827
- // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
11828
- units.push(unit);
11829
- } else {
11830
- // lastUnitStart is undefined => this is the first start code found in this PES packet
11831
- // first check if start code delimiter is overlapping between 2 PES packets,
11832
- // ie it started in last packet (lastState not zero)
11833
- // and ended at the beginning of this PES packet (i <= 4 - lastState)
11834
- const lastUnit = this.getLastNalUnit(track.samples);
11835
- if (lastUnit) {
11836
- if (lastState && i <= 4 - lastState) {
11837
- // start delimiter overlapping between PES packets
11838
- // strip start delimiter bytes from the end of last NAL unit
11839
- // check if lastUnit had a state different from zero
11840
- if (lastUnit.state) {
11841
- // strip last bytes
11842
- lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
12101
+
12102
+ // SPS
12103
+ case 33:
12104
+ push = true;
12105
+ spsfound = true;
12106
+ if (typeof track.params === 'object') {
12107
+ if (track.vps !== undefined && track.vps[0] !== this.initVPS && track.sps !== undefined && !this.matchSPS(track.sps[0], unit.data)) {
12108
+ this.initVPS = track.vps[0];
12109
+ track.sps = track.pps = undefined;
12110
+ }
12111
+ if (!track.sps) {
12112
+ const config = this.readSPS(unit.data);
12113
+ track.width = config.width;
12114
+ track.height = config.height;
12115
+ track.pixelRatio = config.pixelRatio;
12116
+ track.duration = duration;
12117
+ track.codec = config.codecString;
12118
+ track.sps = [];
12119
+ for (const prop in config.params) {
12120
+ track.params[prop] = config.params[prop];
11843
12121
  }
11844
12122
  }
11845
- // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
12123
+ if (track.vps !== undefined && track.vps[0] === this.initVPS) {
12124
+ track.sps.push(unit.data);
12125
+ }
12126
+ }
12127
+ if (!VideoSample) {
12128
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12129
+ }
12130
+ VideoSample.key = true;
12131
+ break;
11846
12132
 
11847
- if (overflow > 0) {
11848
- // logger.log('first NALU found with overflow:' + overflow);
11849
- lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
11850
- lastUnit.state = 0;
12133
+ // PPS
12134
+ case 34:
12135
+ push = true;
12136
+ if (typeof track.params === 'object') {
12137
+ if (!track.pps) {
12138
+ track.pps = [];
12139
+ const config = this.readPPS(unit.data);
12140
+ for (const prop in config) {
12141
+ track.params[prop] = config[prop];
12142
+ }
12143
+ }
12144
+ if (this.initVPS !== null || track.pps.length === 0) {
12145
+ track.pps.push(unit.data);
11851
12146
  }
11852
12147
  }
12148
+ break;
12149
+
12150
+ // ACCESS UNIT DELIMITER
12151
+ case 35:
12152
+ push = true;
12153
+ track.audFound = true;
12154
+ if (VideoSample) {
12155
+ this.pushAccessUnit(VideoSample, track);
12156
+ }
12157
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12158
+ break;
12159
+ default:
12160
+ push = false;
12161
+ if (VideoSample) {
12162
+ VideoSample.debug += 'unknown or irrelevant NAL ' + unit.type + ' ';
12163
+ }
12164
+ break;
12165
+ }
12166
+ if (VideoSample && push) {
12167
+ const units = VideoSample.units;
12168
+ units.push(unit);
12169
+ }
12170
+ });
12171
+ // if last PES packet, push samples
12172
+ if (last && VideoSample) {
12173
+ this.pushAccessUnit(VideoSample, track);
12174
+ this.VideoSample = null;
12175
+ }
12176
+ }
12177
+ getNALuType(data, offset) {
12178
+ return (data[offset] & 0x7e) >>> 1;
12179
+ }
12180
+ ebsp2rbsp(arr) {
12181
+ const dst = new Uint8Array(arr.byteLength);
12182
+ let dstIdx = 0;
12183
+ for (let i = 0; i < arr.byteLength; i++) {
12184
+ if (i >= 2) {
12185
+ // Unescape: Skip 0x03 after 00 00
12186
+ if (arr[i] === 0x03 && arr[i - 1] === 0x00 && arr[i - 2] === 0x00) {
12187
+ continue;
11853
12188
  }
11854
- // check if we can read unit type
11855
- if (i < len) {
11856
- unitType = array[i] & 0x1f;
11857
- // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
11858
- lastUnitStart = i;
11859
- lastUnitType = unitType;
11860
- state = 0;
11861
- } else {
11862
- // not enough byte to read unit type. let's read it on next PES parsing
11863
- state = -1;
11864
- }
11865
- } else {
11866
- state = 0;
11867
12189
  }
12190
+ dst[dstIdx] = arr[i];
12191
+ dstIdx++;
11868
12192
  }
11869
- if (lastUnitStart >= 0 && state >= 0) {
11870
- const unit = {
11871
- data: array.subarray(lastUnitStart, len),
11872
- type: lastUnitType,
11873
- state: state
11874
- };
11875
- units.push(unit);
11876
- // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
12193
+ return new Uint8Array(dst.buffer, 0, dstIdx);
12194
+ }
12195
+ readVPS(vps) {
12196
+ const eg = new ExpGolomb(vps);
12197
+ // remove header
12198
+ eg.readUByte();
12199
+ eg.readUByte();
12200
+ eg.readBits(4); // video_parameter_set_id
12201
+ eg.skipBits(2);
12202
+ eg.readBits(6); // max_layers_minus1
12203
+ const max_sub_layers_minus1 = eg.readBits(3);
12204
+ const temporal_id_nesting_flag = eg.readBoolean();
12205
+ // ...vui fps can be here, but empty fps value is not critical for metadata
12206
+
12207
+ return {
12208
+ numTemporalLayers: max_sub_layers_minus1 + 1,
12209
+ temporalIdNested: temporal_id_nesting_flag
12210
+ };
12211
+ }
12212
+ readSPS(sps) {
12213
+ const eg = new ExpGolomb(this.ebsp2rbsp(sps));
12214
+ eg.readUByte();
12215
+ eg.readUByte();
12216
+ eg.readBits(4); //video_parameter_set_id
12217
+ const max_sub_layers_minus1 = eg.readBits(3);
12218
+ eg.readBoolean(); // temporal_id_nesting_flag
12219
+
12220
+ // profile_tier_level
12221
+ const general_profile_space = eg.readBits(2);
12222
+ const general_tier_flag = eg.readBoolean();
12223
+ const general_profile_idc = eg.readBits(5);
12224
+ const general_profile_compatibility_flags_1 = eg.readUByte();
12225
+ const general_profile_compatibility_flags_2 = eg.readUByte();
12226
+ const general_profile_compatibility_flags_3 = eg.readUByte();
12227
+ const general_profile_compatibility_flags_4 = eg.readUByte();
12228
+ const general_constraint_indicator_flags_1 = eg.readUByte();
12229
+ const general_constraint_indicator_flags_2 = eg.readUByte();
12230
+ const general_constraint_indicator_flags_3 = eg.readUByte();
12231
+ const general_constraint_indicator_flags_4 = eg.readUByte();
12232
+ const general_constraint_indicator_flags_5 = eg.readUByte();
12233
+ const general_constraint_indicator_flags_6 = eg.readUByte();
12234
+ const general_level_idc = eg.readUByte();
12235
+ const sub_layer_profile_present_flags = [];
12236
+ const sub_layer_level_present_flags = [];
12237
+ for (let i = 0; i < max_sub_layers_minus1; i++) {
12238
+ sub_layer_profile_present_flags.push(eg.readBoolean());
12239
+ sub_layer_level_present_flags.push(eg.readBoolean());
12240
+ }
12241
+ if (max_sub_layers_minus1 > 0) {
12242
+ for (let i = max_sub_layers_minus1; i < 8; i++) {
12243
+ eg.readBits(2);
12244
+ }
12245
+ }
12246
+ for (let i = 0; i < max_sub_layers_minus1; i++) {
12247
+ if (sub_layer_profile_present_flags[i]) {
12248
+ eg.readUByte(); // sub_layer_profile_space, sub_layer_tier_flag, sub_layer_profile_idc
12249
+ eg.readUByte();
12250
+ eg.readUByte();
12251
+ eg.readUByte();
12252
+ eg.readUByte(); // sub_layer_profile_compatibility_flag
12253
+ eg.readUByte();
12254
+ eg.readUByte();
12255
+ eg.readUByte();
12256
+ eg.readUByte();
12257
+ eg.readUByte();
12258
+ eg.readUByte();
12259
+ }
12260
+ if (sub_layer_level_present_flags[i]) {
12261
+ eg.readUByte();
12262
+ }
12263
+ }
12264
+ eg.readUEG(); // seq_parameter_set_id
12265
+ const chroma_format_idc = eg.readUEG();
12266
+ if (chroma_format_idc == 3) {
12267
+ eg.skipBits(1); //separate_colour_plane_flag
12268
+ }
12269
+ const pic_width_in_luma_samples = eg.readUEG();
12270
+ const pic_height_in_luma_samples = eg.readUEG();
12271
+ const conformance_window_flag = eg.readBoolean();
12272
+ let pic_left_offset = 0,
12273
+ pic_right_offset = 0,
12274
+ pic_top_offset = 0,
12275
+ pic_bottom_offset = 0;
12276
+ if (conformance_window_flag) {
12277
+ pic_left_offset += eg.readUEG();
12278
+ pic_right_offset += eg.readUEG();
12279
+ pic_top_offset += eg.readUEG();
12280
+ pic_bottom_offset += eg.readUEG();
12281
+ }
12282
+ const bit_depth_luma_minus8 = eg.readUEG();
12283
+ const bit_depth_chroma_minus8 = eg.readUEG();
12284
+ const log2_max_pic_order_cnt_lsb_minus4 = eg.readUEG();
12285
+ const sub_layer_ordering_info_present_flag = eg.readBoolean();
12286
+ for (let i = sub_layer_ordering_info_present_flag ? 0 : max_sub_layers_minus1; i <= max_sub_layers_minus1; i++) {
12287
+ eg.skipUEG(); // max_dec_pic_buffering_minus1[i]
12288
+ eg.skipUEG(); // max_num_reorder_pics[i]
12289
+ eg.skipUEG(); // max_latency_increase_plus1[i]
12290
+ }
12291
+ eg.skipUEG(); // log2_min_luma_coding_block_size_minus3
12292
+ eg.skipUEG(); // log2_diff_max_min_luma_coding_block_size
12293
+ eg.skipUEG(); // log2_min_transform_block_size_minus2
12294
+ eg.skipUEG(); // log2_diff_max_min_transform_block_size
12295
+ eg.skipUEG(); // max_transform_hierarchy_depth_inter
12296
+ eg.skipUEG(); // max_transform_hierarchy_depth_intra
12297
+ const scaling_list_enabled_flag = eg.readBoolean();
12298
+ if (scaling_list_enabled_flag) {
12299
+ const sps_scaling_list_data_present_flag = eg.readBoolean();
12300
+ if (sps_scaling_list_data_present_flag) {
12301
+ for (let sizeId = 0; sizeId < 4; sizeId++) {
12302
+ for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
12303
+ const scaling_list_pred_mode_flag = eg.readBoolean();
12304
+ if (!scaling_list_pred_mode_flag) {
12305
+ eg.readUEG(); // scaling_list_pred_matrix_id_delta
12306
+ } else {
12307
+ const coefNum = Math.min(64, 1 << 4 + (sizeId << 1));
12308
+ if (sizeId > 1) {
12309
+ eg.readEG();
12310
+ }
12311
+ for (let i = 0; i < coefNum; i++) {
12312
+ eg.readEG();
12313
+ }
12314
+ }
12315
+ }
12316
+ }
12317
+ }
11877
12318
  }
11878
- // no NALu found
11879
- if (units.length === 0) {
11880
- // append pes.data to previous NAL unit
11881
- const lastUnit = this.getLastNalUnit(track.samples);
11882
- if (lastUnit) {
11883
- lastUnit.data = appendUint8Array(lastUnit.data, array);
12319
+ eg.readBoolean(); // amp_enabled_flag
12320
+ eg.readBoolean(); // sample_adaptive_offset_enabled_flag
12321
+ const pcm_enabled_flag = eg.readBoolean();
12322
+ if (pcm_enabled_flag) {
12323
+ eg.readUByte();
12324
+ eg.skipUEG();
12325
+ eg.skipUEG();
12326
+ eg.readBoolean();
12327
+ }
12328
+ const num_short_term_ref_pic_sets = eg.readUEG();
12329
+ let num_delta_pocs = 0;
12330
+ for (let i = 0; i < num_short_term_ref_pic_sets; i++) {
12331
+ let inter_ref_pic_set_prediction_flag = false;
12332
+ if (i !== 0) {
12333
+ inter_ref_pic_set_prediction_flag = eg.readBoolean();
12334
+ }
12335
+ if (inter_ref_pic_set_prediction_flag) {
12336
+ if (i === num_short_term_ref_pic_sets) {
12337
+ eg.readUEG();
12338
+ }
12339
+ eg.readBoolean();
12340
+ eg.readUEG();
12341
+ let next_num_delta_pocs = 0;
12342
+ for (let j = 0; j <= num_delta_pocs; j++) {
12343
+ const used_by_curr_pic_flag = eg.readBoolean();
12344
+ let use_delta_flag = false;
12345
+ if (!used_by_curr_pic_flag) {
12346
+ use_delta_flag = eg.readBoolean();
12347
+ }
12348
+ if (used_by_curr_pic_flag || use_delta_flag) {
12349
+ next_num_delta_pocs++;
12350
+ }
12351
+ }
12352
+ num_delta_pocs = next_num_delta_pocs;
12353
+ } else {
12354
+ const num_negative_pics = eg.readUEG();
12355
+ const num_positive_pics = eg.readUEG();
12356
+ num_delta_pocs = num_negative_pics + num_positive_pics;
12357
+ for (let j = 0; j < num_negative_pics; j++) {
12358
+ eg.readUEG();
12359
+ eg.readBoolean();
12360
+ }
12361
+ for (let j = 0; j < num_positive_pics; j++) {
12362
+ eg.readUEG();
12363
+ eg.readBoolean();
12364
+ }
12365
+ }
12366
+ }
12367
+ const long_term_ref_pics_present_flag = eg.readBoolean();
12368
+ if (long_term_ref_pics_present_flag) {
12369
+ const num_long_term_ref_pics_sps = eg.readUEG();
12370
+ for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
12371
+ for (let j = 0; j < log2_max_pic_order_cnt_lsb_minus4 + 4; j++) {
12372
+ eg.readBits(1);
12373
+ }
12374
+ eg.readBits(1);
12375
+ }
12376
+ }
12377
+ let min_spatial_segmentation_idc = 0;
12378
+ let sar_width = 1,
12379
+ sar_height = 1;
12380
+ let fps_fixed = true,
12381
+ fps_den = 1,
12382
+ fps_num = 0;
12383
+ eg.readBoolean(); // sps_temporal_mvp_enabled_flag
12384
+ eg.readBoolean(); // strong_intra_smoothing_enabled_flag
12385
+ let default_display_window_flag = false;
12386
+ const vui_parameters_present_flag = eg.readBoolean();
12387
+ if (vui_parameters_present_flag) {
12388
+ const aspect_ratio_info_present_flag = eg.readBoolean();
12389
+ if (aspect_ratio_info_present_flag) {
12390
+ const aspect_ratio_idc = eg.readUByte();
12391
+ const sar_width_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];
12392
+ const sar_height_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1];
12393
+ if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) {
12394
+ sar_width = sar_width_table[aspect_ratio_idc - 1];
12395
+ sar_height = sar_height_table[aspect_ratio_idc - 1];
12396
+ } else if (aspect_ratio_idc === 255) {
12397
+ sar_width = eg.readBits(16);
12398
+ sar_height = eg.readBits(16);
12399
+ }
12400
+ }
12401
+ const overscan_info_present_flag = eg.readBoolean();
12402
+ if (overscan_info_present_flag) {
12403
+ eg.readBoolean();
12404
+ }
12405
+ const video_signal_type_present_flag = eg.readBoolean();
12406
+ if (video_signal_type_present_flag) {
12407
+ eg.readBits(3);
12408
+ eg.readBoolean();
12409
+ const colour_description_present_flag = eg.readBoolean();
12410
+ if (colour_description_present_flag) {
12411
+ eg.readUByte();
12412
+ eg.readUByte();
12413
+ eg.readUByte();
12414
+ }
12415
+ }
12416
+ const chroma_loc_info_present_flag = eg.readBoolean();
12417
+ if (chroma_loc_info_present_flag) {
12418
+ eg.readUEG();
12419
+ eg.readUEG();
12420
+ }
12421
+ eg.readBoolean(); // neutral_chroma_indication_flag
12422
+ eg.readBoolean(); // field_seq_flag
12423
+ eg.readBoolean(); // frame_field_info_present_flag
12424
+ default_display_window_flag = eg.readBoolean();
12425
+ if (default_display_window_flag) {
12426
+ pic_left_offset += eg.readUEG();
12427
+ pic_right_offset += eg.readUEG();
12428
+ pic_top_offset += eg.readUEG();
12429
+ pic_bottom_offset += eg.readUEG();
12430
+ }
12431
+ const vui_timing_info_present_flag = eg.readBoolean();
12432
+ if (vui_timing_info_present_flag) {
12433
+ fps_den = eg.readBits(32);
12434
+ fps_num = eg.readBits(32);
12435
+ const vui_poc_proportional_to_timing_flag = eg.readBoolean();
12436
+ if (vui_poc_proportional_to_timing_flag) {
12437
+ eg.readUEG();
12438
+ }
12439
+ const vui_hrd_parameters_present_flag = eg.readBoolean();
12440
+ if (vui_hrd_parameters_present_flag) {
12441
+ //const commonInfPresentFlag = true;
12442
+ //if (commonInfPresentFlag) {
12443
+ const nal_hrd_parameters_present_flag = eg.readBoolean();
12444
+ const vcl_hrd_parameters_present_flag = eg.readBoolean();
12445
+ let sub_pic_hrd_params_present_flag = false;
12446
+ if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
12447
+ sub_pic_hrd_params_present_flag = eg.readBoolean();
12448
+ if (sub_pic_hrd_params_present_flag) {
12449
+ eg.readUByte();
12450
+ eg.readBits(5);
12451
+ eg.readBoolean();
12452
+ eg.readBits(5);
12453
+ }
12454
+ eg.readBits(4); // bit_rate_scale
12455
+ eg.readBits(4); // cpb_size_scale
12456
+ if (sub_pic_hrd_params_present_flag) {
12457
+ eg.readBits(4);
12458
+ }
12459
+ eg.readBits(5);
12460
+ eg.readBits(5);
12461
+ eg.readBits(5);
12462
+ }
12463
+ //}
12464
+ for (let i = 0; i <= max_sub_layers_minus1; i++) {
12465
+ fps_fixed = eg.readBoolean(); // fixed_pic_rate_general_flag
12466
+ const fixed_pic_rate_within_cvs_flag = fps_fixed || eg.readBoolean();
12467
+ let low_delay_hrd_flag = false;
12468
+ if (fixed_pic_rate_within_cvs_flag) {
12469
+ eg.readEG();
12470
+ } else {
12471
+ low_delay_hrd_flag = eg.readBoolean();
12472
+ }
12473
+ const cpb_cnt = low_delay_hrd_flag ? 1 : eg.readUEG() + 1;
12474
+ if (nal_hrd_parameters_present_flag) {
12475
+ for (let j = 0; j < cpb_cnt; j++) {
12476
+ eg.readUEG();
12477
+ eg.readUEG();
12478
+ if (sub_pic_hrd_params_present_flag) {
12479
+ eg.readUEG();
12480
+ eg.readUEG();
12481
+ }
12482
+ eg.skipBits(1);
12483
+ }
12484
+ }
12485
+ if (vcl_hrd_parameters_present_flag) {
12486
+ for (let j = 0; j < cpb_cnt; j++) {
12487
+ eg.readUEG();
12488
+ eg.readUEG();
12489
+ if (sub_pic_hrd_params_present_flag) {
12490
+ eg.readUEG();
12491
+ eg.readUEG();
12492
+ }
12493
+ eg.skipBits(1);
12494
+ }
12495
+ }
12496
+ }
12497
+ }
11884
12498
  }
12499
+ const bitstream_restriction_flag = eg.readBoolean();
12500
+ if (bitstream_restriction_flag) {
12501
+ eg.readBoolean(); // tiles_fixed_structure_flag
12502
+ eg.readBoolean(); // motion_vectors_over_pic_boundaries_flag
12503
+ eg.readBoolean(); // restricted_ref_pic_lists_flag
12504
+ min_spatial_segmentation_idc = eg.readUEG();
12505
+ }
12506
+ }
12507
+ let width = pic_width_in_luma_samples,
12508
+ height = pic_height_in_luma_samples;
12509
+ if (conformance_window_flag || default_display_window_flag) {
12510
+ let chroma_scale_w = 1,
12511
+ chroma_scale_h = 1;
12512
+ if (chroma_format_idc === 1) {
12513
+ // YUV 420
12514
+ chroma_scale_w = chroma_scale_h = 2;
12515
+ } else if (chroma_format_idc == 2) {
12516
+ // YUV 422
12517
+ chroma_scale_w = 2;
12518
+ }
12519
+ width = pic_width_in_luma_samples - chroma_scale_w * pic_right_offset - chroma_scale_w * pic_left_offset;
12520
+ height = pic_height_in_luma_samples - chroma_scale_h * pic_bottom_offset - chroma_scale_h * pic_top_offset;
12521
+ }
12522
+ const profile_space_string = general_profile_space ? ['A', 'B', 'C'][general_profile_space] : '';
12523
+ 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;
12524
+ let profile_compatibility_rev = 0;
12525
+ for (let i = 0; i < 32; i++) {
12526
+ profile_compatibility_rev = (profile_compatibility_rev | (profile_compatibility_buf >> i & 1) << 31 - i) >>> 0; // reverse bit position (and cast as UInt32)
12527
+ }
12528
+ let profile_compatibility_flags_string = profile_compatibility_rev.toString(16);
12529
+ if (general_profile_idc === 1 && profile_compatibility_flags_string === '2') {
12530
+ profile_compatibility_flags_string = '6';
12531
+ }
12532
+ const tier_flag_string = general_tier_flag ? 'H' : 'L';
12533
+ return {
12534
+ codecString: `hvc1.${profile_space_string}${general_profile_idc}.${profile_compatibility_flags_string}.${tier_flag_string}${general_level_idc}.B0`,
12535
+ params: {
12536
+ general_tier_flag,
12537
+ general_profile_idc,
12538
+ general_profile_space,
12539
+ general_profile_compatibility_flags: [general_profile_compatibility_flags_1, general_profile_compatibility_flags_2, general_profile_compatibility_flags_3, general_profile_compatibility_flags_4],
12540
+ 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],
12541
+ general_level_idc,
12542
+ bit_depth: bit_depth_luma_minus8 + 8,
12543
+ bit_depth_luma_minus8,
12544
+ bit_depth_chroma_minus8,
12545
+ min_spatial_segmentation_idc,
12546
+ chroma_format_idc: chroma_format_idc,
12547
+ frame_rate: {
12548
+ fixed: fps_fixed,
12549
+ fps: fps_num / fps_den
12550
+ }
12551
+ },
12552
+ width,
12553
+ height,
12554
+ pixelRatio: [sar_width, sar_height]
12555
+ };
12556
+ }
12557
+ readPPS(pps) {
12558
+ const eg = new ExpGolomb(this.ebsp2rbsp(pps));
12559
+ eg.readUByte();
12560
+ eg.readUByte();
12561
+ eg.skipUEG(); // pic_parameter_set_id
12562
+ eg.skipUEG(); // seq_parameter_set_id
12563
+ eg.skipBits(2); // dependent_slice_segments_enabled_flag, output_flag_present_flag
12564
+ eg.skipBits(3); // num_extra_slice_header_bits
12565
+ eg.skipBits(2); // sign_data_hiding_enabled_flag, cabac_init_present_flag
12566
+ eg.skipUEG();
12567
+ eg.skipUEG();
12568
+ eg.skipEG(); // init_qp_minus26
12569
+ eg.skipBits(2); // constrained_intra_pred_flag, transform_skip_enabled_flag
12570
+ const cu_qp_delta_enabled_flag = eg.readBoolean();
12571
+ if (cu_qp_delta_enabled_flag) {
12572
+ eg.skipUEG();
12573
+ }
12574
+ eg.skipEG(); // cb_qp_offset
12575
+ eg.skipEG(); // cr_qp_offset
12576
+ eg.skipBits(4); // pps_slice_chroma_qp_offsets_present_flag, weighted_pred_flag, weighted_bipred_flag, transquant_bypass_enabled_flag
12577
+ const tiles_enabled_flag = eg.readBoolean();
12578
+ const entropy_coding_sync_enabled_flag = eg.readBoolean();
12579
+ let parallelismType = 1; // slice-based parallel decoding
12580
+ if (entropy_coding_sync_enabled_flag && tiles_enabled_flag) {
12581
+ parallelismType = 0; // mixed-type parallel decoding
12582
+ } else if (entropy_coding_sync_enabled_flag) {
12583
+ parallelismType = 3; // wavefront-based parallel decoding
12584
+ } else if (tiles_enabled_flag) {
12585
+ parallelismType = 2; // tile-based parallel decoding
11885
12586
  }
11886
- track.naluState = state;
11887
- return units;
12587
+ return {
12588
+ parallelismType
12589
+ };
12590
+ }
12591
+ matchSPS(sps1, sps2) {
12592
+ // compare without headers and VPS related params
12593
+ return String.fromCharCode.apply(null, sps1).substr(3) === String.fromCharCode.apply(null, sps2).substr(3);
11888
12594
  }
11889
12595
  }
11890
12596
 
@@ -11902,7 +12608,7 @@ class SampleAesDecrypter {
11902
12608
  });
11903
12609
  }
11904
12610
  decryptBuffer(encryptedData) {
11905
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
12611
+ return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
11906
12612
  }
11907
12613
 
11908
12614
  // AAC - encrypt all full 16 bytes blocks starting from offset 16
@@ -12016,7 +12722,7 @@ class TSDemuxer {
12016
12722
  this.observer = observer;
12017
12723
  this.config = config;
12018
12724
  this.typeSupported = typeSupported;
12019
- this.videoParser = new AvcVideoParser();
12725
+ this.videoParser = null;
12020
12726
  }
12021
12727
  static probe(data) {
12022
12728
  const syncOffset = TSDemuxer.syncOffset(data);
@@ -12181,7 +12887,19 @@ class TSDemuxer {
12181
12887
  case videoPid:
12182
12888
  if (stt) {
12183
12889
  if (videoData && (pes = parsePES(videoData))) {
12184
- this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
12890
+ if (this.videoParser === null) {
12891
+ switch (videoTrack.segmentCodec) {
12892
+ case 'avc':
12893
+ this.videoParser = new AvcVideoParser();
12894
+ break;
12895
+ case 'hevc':
12896
+ this.videoParser = new HevcVideoParser();
12897
+ break;
12898
+ }
12899
+ }
12900
+ if (this.videoParser !== null) {
12901
+ this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
12902
+ }
12185
12903
  }
12186
12904
  videoData = {
12187
12905
  data: [],
@@ -12348,8 +13066,20 @@ class TSDemuxer {
12348
13066
  // try to parse last PES packets
12349
13067
  let pes;
12350
13068
  if (videoData && (pes = parsePES(videoData))) {
12351
- this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
12352
- videoTrack.pesData = null;
13069
+ if (this.videoParser === null) {
13070
+ switch (videoTrack.segmentCodec) {
13071
+ case 'avc':
13072
+ this.videoParser = new AvcVideoParser();
13073
+ break;
13074
+ case 'hevc':
13075
+ this.videoParser = new HevcVideoParser();
13076
+ break;
13077
+ }
13078
+ }
13079
+ if (this.videoParser !== null) {
13080
+ this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
13081
+ videoTrack.pesData = null;
13082
+ }
12353
13083
  } else {
12354
13084
  // either avcData null or PES truncated, keep it for next frag parsing
12355
13085
  videoTrack.pesData = videoData;
@@ -12682,7 +13412,12 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
12682
13412
  logger.warn('Unsupported EC-3 in M2TS found');
12683
13413
  break;
12684
13414
  case 0x24:
12685
- logger.warn('Unsupported HEVC in M2TS found');
13415
+ // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
13416
+ if (result.videoPid === -1) {
13417
+ result.videoPid = pid;
13418
+ result.segmentVideoCodec = 'hevc';
13419
+ logger.log('HEVC in M2TS found');
13420
+ }
12686
13421
  break;
12687
13422
  }
12688
13423
  // move to the next table entry
@@ -12905,6 +13640,8 @@ class MP4 {
12905
13640
  avc1: [],
12906
13641
  // codingname
12907
13642
  avcC: [],
13643
+ hvc1: [],
13644
+ hvcC: [],
12908
13645
  btrt: [],
12909
13646
  dinf: [],
12910
13647
  dref: [],
@@ -13329,8 +14066,10 @@ class MP4 {
13329
14066
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
13330
14067
  }
13331
14068
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
13332
- } else {
14069
+ } else if (track.segmentCodec === 'avc') {
13333
14070
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
14071
+ } else {
14072
+ return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
13334
14073
  }
13335
14074
  }
13336
14075
  static tkhd(track) {
@@ -13468,6 +14207,84 @@ class MP4 {
13468
14207
  const result = appendUint8Array(MP4.FTYP, movie);
13469
14208
  return result;
13470
14209
  }
14210
+ static hvc1(track) {
14211
+ const ps = track.params;
14212
+ const units = [track.vps, track.sps, track.pps];
14213
+ const NALuLengthSize = 4;
14214
+ 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]);
14215
+
14216
+ // compute hvcC size in bytes
14217
+ let length = config.length;
14218
+ for (let i = 0; i < units.length; i += 1) {
14219
+ length += 3;
14220
+ for (let j = 0; j < units[i].length; j += 1) {
14221
+ length += 2 + units[i][j].length;
14222
+ }
14223
+ }
14224
+ const hvcC = new Uint8Array(length);
14225
+ hvcC.set(config, 0);
14226
+ length = config.length;
14227
+ // append parameter set units: one vps, one or more sps and pps
14228
+ const iMax = units.length - 1;
14229
+ for (let i = 0; i < units.length; i += 1) {
14230
+ hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
14231
+ length += 3;
14232
+ for (let j = 0; j < units[i].length; j += 1) {
14233
+ hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
14234
+ length += 2;
14235
+ hvcC.set(units[i][j], length);
14236
+ length += units[i][j].length;
14237
+ }
14238
+ }
14239
+ const hvcc = MP4.box(MP4.types.hvcC, hvcC);
14240
+ const width = track.width;
14241
+ const height = track.height;
14242
+ const hSpacing = track.pixelRatio[0];
14243
+ const vSpacing = track.pixelRatio[1];
14244
+ return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
14245
+ // reserved
14246
+ 0x00, 0x00, 0x00,
14247
+ // reserved
14248
+ 0x00, 0x01,
14249
+ // data_reference_index
14250
+ 0x00, 0x00,
14251
+ // pre_defined
14252
+ 0x00, 0x00,
14253
+ // reserved
14254
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14255
+ // pre_defined
14256
+ width >> 8 & 0xff, width & 0xff,
14257
+ // width
14258
+ height >> 8 & 0xff, height & 0xff,
14259
+ // height
14260
+ 0x00, 0x48, 0x00, 0x00,
14261
+ // horizresolution
14262
+ 0x00, 0x48, 0x00, 0x00,
14263
+ // vertresolution
14264
+ 0x00, 0x00, 0x00, 0x00,
14265
+ // reserved
14266
+ 0x00, 0x01,
14267
+ // frame_count
14268
+ 0x12, 0x64, 0x61, 0x69, 0x6c,
14269
+ // dailymotion/hls.js
14270
+ 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,
14271
+ // compressorname
14272
+ 0x00, 0x18,
14273
+ // depth = 24
14274
+ 0x11, 0x11]),
14275
+ // pre_defined = -1
14276
+ hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
14277
+ // bufferSizeDB
14278
+ 0x00, 0x2d, 0xc6, 0xc0,
14279
+ // maxBitrate
14280
+ 0x00, 0x2d, 0xc6, 0xc0])),
14281
+ // avgBitrate
14282
+ MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
14283
+ // hSpacing
14284
+ hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
14285
+ // vSpacing
14286
+ vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
14287
+ }
13471
14288
  }
13472
14289
  MP4.types = void 0;
13473
14290
  MP4.HDLR_TYPES = void 0;
@@ -13849,9 +14666,9 @@ class MP4Remuxer {
13849
14666
  const foundOverlap = delta < -1;
13850
14667
  if (foundHole || foundOverlap) {
13851
14668
  if (foundHole) {
13852
- logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
14669
+ logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
13853
14670
  } else {
13854
- logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
14671
+ logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
13855
14672
  }
13856
14673
  if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
13857
14674
  firstDTS = nextAvcDts;
@@ -13860,12 +14677,24 @@ class MP4Remuxer {
13860
14677
  inputSamples[0].dts = firstDTS;
13861
14678
  inputSamples[0].pts = firstPTS;
13862
14679
  } else {
14680
+ let isPTSOrderRetained = true;
13863
14681
  for (let i = 0; i < inputSamples.length; i++) {
13864
- if (inputSamples[i].dts > firstPTS) {
14682
+ if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
13865
14683
  break;
13866
14684
  }
14685
+ const prevPTS = inputSamples[i].pts;
13867
14686
  inputSamples[i].dts -= delta;
13868
14687
  inputSamples[i].pts -= delta;
14688
+
14689
+ // check to see if this sample's PTS order has changed
14690
+ // relative to the next one
14691
+ if (i < inputSamples.length - 1) {
14692
+ const nextSamplePTS = inputSamples[i + 1].pts;
14693
+ const currentSamplePTS = inputSamples[i].pts;
14694
+ const currentOrder = nextSamplePTS <= currentSamplePTS;
14695
+ const prevOrder = nextSamplePTS <= prevPTS;
14696
+ isPTSOrderRetained = currentOrder == prevOrder;
14697
+ }
13869
14698
  }
13870
14699
  }
13871
14700
  logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
@@ -14013,7 +14842,7 @@ class MP4Remuxer {
14013
14842
  }
14014
14843
  }
14015
14844
  }
14016
- // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14845
+ // next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14017
14846
  mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
14018
14847
  this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
14019
14848
  this.videoSampleDuration = mp4SampleDuration;
@@ -14146,7 +14975,7 @@ class MP4Remuxer {
14146
14975
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
14147
14976
  for (let j = 0; j < missing; j++) {
14148
14977
  const newStamp = Math.max(nextPts, 0);
14149
- let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
14978
+ let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14150
14979
  if (!fillFrame) {
14151
14980
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
14152
14981
  fillFrame = sample.unit.subarray();
@@ -14274,7 +15103,7 @@ class MP4Remuxer {
14274
15103
  // samples count of this segment's duration
14275
15104
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
14276
15105
  // silent frame
14277
- const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
15106
+ const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14278
15107
  logger.warn('[mp4-remuxer]: remux empty Audio');
14279
15108
  // Can't remux if we can't generate a silent frame...
14280
15109
  if (!silentFrame) {
@@ -14668,13 +15497,15 @@ class Transmuxer {
14668
15497
  initSegmentData
14669
15498
  } = transmuxConfig;
14670
15499
  const keyData = getEncryptionType(uintData, decryptdata);
14671
- if (keyData && keyData.method === 'AES-128') {
15500
+ if (keyData && isFullSegmentEncryption(keyData.method)) {
14672
15501
  const decrypter = this.getDecrypter();
15502
+ const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
15503
+
14673
15504
  // Software decryption is synchronous; webCrypto is not
14674
15505
  if (decrypter.isSync()) {
14675
15506
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
14676
15507
  // data is handled in the flush() call
14677
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
15508
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
14678
15509
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
14679
15510
  const loadingParts = chunkMeta.part > -1;
14680
15511
  if (loadingParts) {
@@ -14686,7 +15517,7 @@ class Transmuxer {
14686
15517
  }
14687
15518
  uintData = new Uint8Array(decryptedData);
14688
15519
  } else {
14689
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
15520
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
14690
15521
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
14691
15522
  // the decrypted data has been transmuxed
14692
15523
  const result = this.push(decryptedData, null, chunkMeta);
@@ -15340,14 +16171,7 @@ class TransmuxerInterface {
15340
16171
  this.observer = new EventEmitter();
15341
16172
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
15342
16173
  this.observer.on(Events.ERROR, forwardMessage);
15343
- const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
15344
- isTypeSupported: () => false
15345
- };
15346
- const m2tsTypeSupported = {
15347
- mpeg: MediaSource.isTypeSupported('audio/mpeg'),
15348
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
15349
- ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
15350
- };
16174
+ const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
15351
16175
 
15352
16176
  // navigator.vendor is not always available in Web Worker
15353
16177
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -15635,7 +16459,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
15635
16459
 
15636
16460
  class AudioStreamController extends BaseStreamController {
15637
16461
  constructor(hls, fragmentTracker, keyLoader) {
15638
- super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);
16462
+ super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
15639
16463
  this.videoBuffer = null;
15640
16464
  this.videoTrackCC = -1;
15641
16465
  this.waitingVideoCC = -1;
@@ -15647,27 +16471,24 @@ class AudioStreamController extends BaseStreamController {
15647
16471
  this.flushing = false;
15648
16472
  this.bufferFlushed = false;
15649
16473
  this.cachedTrackLoadedData = null;
15650
- this._registerListeners();
16474
+ this.registerListeners();
15651
16475
  }
15652
16476
  onHandlerDestroying() {
15653
- this._unregisterListeners();
16477
+ this.unregisterListeners();
15654
16478
  super.onHandlerDestroying();
15655
16479
  this.mainDetails = null;
15656
16480
  this.bufferedTrack = null;
15657
16481
  this.switchingTrack = null;
15658
16482
  }
15659
- _registerListeners() {
16483
+ registerListeners() {
16484
+ super.registerListeners();
15660
16485
  const {
15661
16486
  hls
15662
16487
  } = this;
15663
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
15664
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
15665
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
15666
16488
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
15667
16489
  hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
15668
16490
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
15669
16491
  hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15670
- hls.on(Events.ERROR, this.onError, this);
15671
16492
  hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
15672
16493
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
15673
16494
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -15675,18 +16496,18 @@ class AudioStreamController extends BaseStreamController {
15675
16496
  hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
15676
16497
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
15677
16498
  }
15678
- _unregisterListeners() {
16499
+ unregisterListeners() {
15679
16500
  const {
15680
16501
  hls
15681
16502
  } = this;
15682
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
15683
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
15684
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16503
+ if (!hls) {
16504
+ return;
16505
+ }
16506
+ super.unregisterListeners();
15685
16507
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
15686
16508
  hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
15687
16509
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
15688
16510
  hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15689
- hls.off(Events.ERROR, this.onError, this);
15690
16511
  hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
15691
16512
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
15692
16513
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -15855,12 +16676,13 @@ class AudioStreamController extends BaseStreamController {
15855
16676
  } = this;
15856
16677
  const config = hls.config;
15857
16678
 
15858
- // 1. if video not attached AND
16679
+ // 1. if buffering is suspended
16680
+ // 2. if video not attached AND
15859
16681
  // start fragment already requested OR start frag prefetch not enabled
15860
- // 2. if tracks or track not loaded and selected
16682
+ // 3. if tracks or track not loaded and selected
15861
16683
  // then exit loop
15862
16684
  // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
15863
- if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
16685
+ if (!this.buffering || !media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
15864
16686
  return;
15865
16687
  }
15866
16688
  const levelInfo = levels[trackId];
@@ -16418,7 +17240,7 @@ class AudioStreamController extends BaseStreamController {
16418
17240
 
16419
17241
  class AudioTrackController extends BasePlaylistController {
16420
17242
  constructor(hls) {
16421
- super(hls, '[audio-track-controller]');
17243
+ super(hls, 'audio-track-controller');
16422
17244
  this.tracks = [];
16423
17245
  this.groupIds = null;
16424
17246
  this.tracksInGroup = [];
@@ -16737,26 +17559,23 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
16737
17559
 
16738
17560
  class SubtitleStreamController extends BaseStreamController {
16739
17561
  constructor(hls, fragmentTracker, keyLoader) {
16740
- super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);
17562
+ super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
16741
17563
  this.currentTrackId = -1;
16742
17564
  this.tracksBuffered = [];
16743
17565
  this.mainDetails = null;
16744
- this._registerListeners();
17566
+ this.registerListeners();
16745
17567
  }
16746
17568
  onHandlerDestroying() {
16747
- this._unregisterListeners();
17569
+ this.unregisterListeners();
16748
17570
  super.onHandlerDestroying();
16749
17571
  this.mainDetails = null;
16750
17572
  }
16751
- _registerListeners() {
17573
+ registerListeners() {
17574
+ super.registerListeners();
16752
17575
  const {
16753
17576
  hls
16754
17577
  } = this;
16755
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
16756
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
16757
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16758
17578
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16759
- hls.on(Events.ERROR, this.onError, this);
16760
17579
  hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
16761
17580
  hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
16762
17581
  hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -16764,15 +17583,12 @@ class SubtitleStreamController extends BaseStreamController {
16764
17583
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
16765
17584
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
16766
17585
  }
16767
- _unregisterListeners() {
17586
+ unregisterListeners() {
17587
+ super.unregisterListeners();
16768
17588
  const {
16769
17589
  hls
16770
17590
  } = this;
16771
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
16772
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
16773
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16774
17591
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16775
- hls.off(Events.ERROR, this.onError, this);
16776
17592
  hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
16777
17593
  hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
16778
17594
  hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -16999,10 +17815,10 @@ class SubtitleStreamController extends BaseStreamController {
16999
17815
  return;
17000
17816
  }
17001
17817
  // check to see if the payload needs to be decrypted
17002
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
17818
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
17003
17819
  const startTime = performance.now();
17004
17820
  // decrypt the subtitles
17005
- this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
17821
+ this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
17006
17822
  hls.trigger(Events.ERROR, {
17007
17823
  type: ErrorTypes.MEDIA_ERROR,
17008
17824
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -17136,7 +17952,7 @@ class BufferableInstance {
17136
17952
 
17137
17953
  class SubtitleTrackController extends BasePlaylistController {
17138
17954
  constructor(hls) {
17139
- super(hls, '[subtitle-track-controller]');
17955
+ super(hls, 'subtitle-track-controller');
17140
17956
  this.media = null;
17141
17957
  this.tracks = [];
17142
17958
  this.groupIds = null;
@@ -17145,10 +17961,10 @@ class SubtitleTrackController extends BasePlaylistController {
17145
17961
  this.currentTrack = null;
17146
17962
  this.selectDefaultTrack = true;
17147
17963
  this.queuedDefaultTrack = -1;
17148
- this.asyncPollTrackChange = () => this.pollTrackChange(0);
17149
17964
  this.useTextTrackPolling = false;
17150
17965
  this.subtitlePollingInterval = -1;
17151
17966
  this._subtitleDisplay = true;
17967
+ this.asyncPollTrackChange = () => this.pollTrackChange(0);
17152
17968
  this.onTextTracksChanged = () => {
17153
17969
  if (!this.useTextTrackPolling) {
17154
17970
  self.clearInterval(this.subtitlePollingInterval);
@@ -17182,6 +17998,7 @@ class SubtitleTrackController extends BasePlaylistController {
17182
17998
  this.tracks.length = 0;
17183
17999
  this.tracksInGroup.length = 0;
17184
18000
  this.currentTrack = null;
18001
+ // @ts-ignore
17185
18002
  this.onTextTracksChanged = this.asyncPollTrackChange = null;
17186
18003
  super.destroy();
17187
18004
  }
@@ -17642,8 +18459,9 @@ class BufferOperationQueue {
17642
18459
  }
17643
18460
 
17644
18461
  const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
17645
- class BufferController {
18462
+ class BufferController extends Logger {
17646
18463
  constructor(hls) {
18464
+ super('buffer-controller', hls.logger);
17647
18465
  // The level details used to determine duration, target-duration and live
17648
18466
  this.details = null;
17649
18467
  // cache the self generated object url to detect hijack of video tag
@@ -17673,9 +18491,6 @@ class BufferController {
17673
18491
  this.tracks = {};
17674
18492
  this.pendingTracks = {};
17675
18493
  this.sourceBuffer = void 0;
17676
- this.log = void 0;
17677
- this.warn = void 0;
17678
- this.error = void 0;
17679
18494
  this._onEndStreaming = event => {
17680
18495
  if (!this.hls) {
17681
18496
  return;
@@ -17721,15 +18536,11 @@ class BufferController {
17721
18536
  _objectUrl
17722
18537
  } = this;
17723
18538
  if (mediaSrc !== _objectUrl) {
17724
- logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
18539
+ this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
17725
18540
  }
17726
18541
  };
17727
18542
  this.hls = hls;
17728
- const logPrefix = '[buffer-controller]';
17729
18543
  this.appendSource = hls.config.preferManagedMediaSource;
17730
- this.log = logger.log.bind(logger, logPrefix);
17731
- this.warn = logger.warn.bind(logger, logPrefix);
17732
- this.error = logger.error.bind(logger, logPrefix);
17733
18544
  this._initSourceBuffer();
17734
18545
  this.registerListeners();
17735
18546
  }
@@ -17742,6 +18553,12 @@ class BufferController {
17742
18553
  this.lastMpegAudioChunk = null;
17743
18554
  // @ts-ignore
17744
18555
  this.hls = null;
18556
+ // @ts-ignore
18557
+ this._onMediaSourceOpen = this._onMediaSourceClose = null;
18558
+ // @ts-ignore
18559
+ this._onMediaSourceEnded = null;
18560
+ // @ts-ignore
18561
+ this._onStartStreaming = this._onEndStreaming = null;
17745
18562
  }
17746
18563
  registerListeners() {
17747
18564
  const {
@@ -17904,6 +18721,7 @@ class BufferController {
17904
18721
  this.resetBuffer(type);
17905
18722
  });
17906
18723
  this._initSourceBuffer();
18724
+ this.hls.resumeBuffering();
17907
18725
  }
17908
18726
  resetBuffer(type) {
17909
18727
  const sb = this.sourceBuffer[type];
@@ -21006,14 +21824,12 @@ class TimelineController {
21006
21824
  this.cea608Parser1 = this.cea608Parser2 = undefined;
21007
21825
  }
21008
21826
  initCea608Parsers() {
21009
- if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) {
21010
- const channel1 = new OutputFilter(this, 'textTrack1');
21011
- const channel2 = new OutputFilter(this, 'textTrack2');
21012
- const channel3 = new OutputFilter(this, 'textTrack3');
21013
- const channel4 = new OutputFilter(this, 'textTrack4');
21014
- this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
21015
- this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
21016
- }
21827
+ const channel1 = new OutputFilter(this, 'textTrack1');
21828
+ const channel2 = new OutputFilter(this, 'textTrack2');
21829
+ const channel3 = new OutputFilter(this, 'textTrack3');
21830
+ const channel4 = new OutputFilter(this, 'textTrack4');
21831
+ this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
21832
+ this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
21017
21833
  }
21018
21834
  addCues(trackName, startTime, endTime, screen, cueRanges) {
21019
21835
  // skip cues which overlap more than 50% with previously parsed time ranges
@@ -21251,7 +22067,7 @@ class TimelineController {
21251
22067
  if (inUseTracks != null && inUseTracks.length) {
21252
22068
  const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
21253
22069
  if (unusedTextTracks.length) {
21254
- logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
22070
+ this.hls.logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
21255
22071
  }
21256
22072
  }
21257
22073
  } else if (this.tracks.length) {
@@ -21296,26 +22112,23 @@ class TimelineController {
21296
22112
  return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
21297
22113
  }
21298
22114
  onFragLoading(event, data) {
21299
- this.initCea608Parsers();
21300
- const {
21301
- cea608Parser1,
21302
- cea608Parser2,
21303
- lastCc,
21304
- lastSn,
21305
- lastPartIndex
21306
- } = this;
21307
- if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
21308
- return;
21309
- }
21310
22115
  // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
21311
- if (data.frag.type === PlaylistLevelType.MAIN) {
22116
+ if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
21312
22117
  var _data$part$index, _data$part;
22118
+ const {
22119
+ cea608Parser1,
22120
+ cea608Parser2,
22121
+ lastSn
22122
+ } = this;
22123
+ if (!cea608Parser1 || !cea608Parser2) {
22124
+ return;
22125
+ }
21313
22126
  const {
21314
22127
  cc,
21315
22128
  sn
21316
22129
  } = data.frag;
21317
- const partIndex = (_data$part$index = data == null ? void 0 : (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
21318
- if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
22130
+ const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
22131
+ if (!(sn === lastSn + 1 || sn === lastSn && partIndex === this.lastPartIndex + 1 || cc === this.lastCc)) {
21319
22132
  cea608Parser1.reset();
21320
22133
  cea608Parser2.reset();
21321
22134
  }
@@ -21372,7 +22185,7 @@ class TimelineController {
21372
22185
  frag: frag
21373
22186
  });
21374
22187
  }, error => {
21375
- logger.log(`Failed to parse IMSC1: ${error}`);
22188
+ hls.logger.log(`Failed to parse IMSC1: ${error}`);
21376
22189
  hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
21377
22190
  success: false,
21378
22191
  frag: frag,
@@ -21413,7 +22226,7 @@ class TimelineController {
21413
22226
  this._fallbackToIMSC1(frag, payload);
21414
22227
  }
21415
22228
  // Something went wrong while parsing. Trigger event with success false.
21416
- logger.log(`Failed to parse VTT cue: ${error}`);
22229
+ hls.logger.log(`Failed to parse VTT cue: ${error}`);
21417
22230
  if (missingInitPTS && maxAvCC > frag.cc) {
21418
22231
  return;
21419
22232
  }
@@ -21474,12 +22287,7 @@ class TimelineController {
21474
22287
  this.captionsTracks = {};
21475
22288
  }
21476
22289
  onFragParsingUserdata(event, data) {
21477
- this.initCea608Parsers();
21478
- const {
21479
- cea608Parser1,
21480
- cea608Parser2
21481
- } = this;
21482
- if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
22290
+ if (!this.enabled || !this.config.enableCEA708Captions) {
21483
22291
  return;
21484
22292
  }
21485
22293
  const {
@@ -21494,9 +22302,12 @@ class TimelineController {
21494
22302
  for (let i = 0; i < samples.length; i++) {
21495
22303
  const ccBytes = samples[i].bytes;
21496
22304
  if (ccBytes) {
22305
+ if (!this.cea608Parser1) {
22306
+ this.initCea608Parsers();
22307
+ }
21497
22308
  const ccdatas = this.extractCea608Data(ccBytes);
21498
- cea608Parser1.addData(samples[i].pts, ccdatas[0]);
21499
- cea608Parser2.addData(samples[i].pts, ccdatas[1]);
22309
+ this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
22310
+ this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
21500
22311
  }
21501
22312
  }
21502
22313
  }
@@ -21692,7 +22503,7 @@ class CapLevelController {
21692
22503
  const hls = this.hls;
21693
22504
  const maxLevel = this.getMaxLevel(levels.length - 1);
21694
22505
  if (maxLevel !== this.autoLevelCapping) {
21695
- logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
22506
+ hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
21696
22507
  }
21697
22508
  hls.autoLevelCapping = maxLevel;
21698
22509
  if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
@@ -21870,10 +22681,10 @@ class FPSController {
21870
22681
  totalDroppedFrames: droppedFrames
21871
22682
  });
21872
22683
  if (droppedFPS > 0) {
21873
- // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
22684
+ // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
21874
22685
  if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
21875
22686
  let currentLevel = hls.currentLevel;
21876
- logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
22687
+ hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
21877
22688
  if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
21878
22689
  currentLevel = currentLevel - 1;
21879
22690
  hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
@@ -21905,7 +22716,6 @@ class FPSController {
21905
22716
  }
21906
22717
  }
21907
22718
 
21908
- const LOGGER_PREFIX = '[eme]';
21909
22719
  /**
21910
22720
  * Controller to deal with encrypted media extensions (EME)
21911
22721
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
@@ -21913,8 +22723,9 @@ const LOGGER_PREFIX = '[eme]';
21913
22723
  * @class
21914
22724
  * @constructor
21915
22725
  */
21916
- class EMEController {
22726
+ class EMEController extends Logger {
21917
22727
  constructor(hls) {
22728
+ super('eme', hls.logger);
21918
22729
  this.hls = void 0;
21919
22730
  this.config = void 0;
21920
22731
  this.media = null;
@@ -21924,12 +22735,100 @@ class EMEController {
21924
22735
  this.mediaKeySessions = [];
21925
22736
  this.keyIdToKeySessionPromise = {};
21926
22737
  this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
21927
- this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
21928
- this.onWaitingForKey = this._onWaitingForKey.bind(this);
21929
- this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
21930
- this.log = logger.log.bind(logger, LOGGER_PREFIX);
21931
- this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
21932
- this.error = logger.error.bind(logger, LOGGER_PREFIX);
22738
+ this.onMediaEncrypted = event => {
22739
+ const {
22740
+ initDataType,
22741
+ initData
22742
+ } = event;
22743
+ this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22744
+
22745
+ // Ignore event when initData is null
22746
+ if (initData === null) {
22747
+ return;
22748
+ }
22749
+ let keyId;
22750
+ let keySystemDomain;
22751
+ if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22752
+ // Match sinf keyId to playlist skd://keyId=
22753
+ const json = bin2str(new Uint8Array(initData));
22754
+ try {
22755
+ const sinf = base64Decode(JSON.parse(json).sinf);
22756
+ const tenc = parseSinf(new Uint8Array(sinf));
22757
+ if (!tenc) {
22758
+ return;
22759
+ }
22760
+ keyId = tenc.subarray(8, 24);
22761
+ keySystemDomain = KeySystems.FAIRPLAY;
22762
+ } catch (error) {
22763
+ this.warn('Failed to parse sinf "encrypted" event message initData');
22764
+ return;
22765
+ }
22766
+ } else {
22767
+ // Support clear-lead key-session creation (otherwise depend on playlist keys)
22768
+ const psshInfo = parsePssh(initData);
22769
+ if (psshInfo === null) {
22770
+ return;
22771
+ }
22772
+ if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22773
+ keyId = psshInfo.data.subarray(8, 24);
22774
+ }
22775
+ keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22776
+ }
22777
+ if (!keySystemDomain || !keyId) {
22778
+ return;
22779
+ }
22780
+ const keyIdHex = Hex.hexDump(keyId);
22781
+ const {
22782
+ keyIdToKeySessionPromise,
22783
+ mediaKeySessions
22784
+ } = this;
22785
+ let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22786
+ for (let i = 0; i < mediaKeySessions.length; i++) {
22787
+ // Match playlist key
22788
+ const keyContext = mediaKeySessions[i];
22789
+ const decryptdata = keyContext.decryptdata;
22790
+ if (decryptdata.pssh || !decryptdata.keyId) {
22791
+ continue;
22792
+ }
22793
+ const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22794
+ if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22795
+ keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22796
+ delete keyIdToKeySessionPromise[oldKeyIdHex];
22797
+ decryptdata.pssh = new Uint8Array(initData);
22798
+ decryptdata.keyId = keyId;
22799
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22800
+ return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22801
+ });
22802
+ break;
22803
+ }
22804
+ }
22805
+ if (!keySessionContextPromise) {
22806
+ // Clear-lead key (not encountered in playlist)
22807
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22808
+ keySystem,
22809
+ mediaKeys
22810
+ }) => {
22811
+ var _keySystemToKeySystem;
22812
+ this.throwIfDestroyed();
22813
+ const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22814
+ decryptdata.pssh = new Uint8Array(initData);
22815
+ decryptdata.keyId = keyId;
22816
+ return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22817
+ this.throwIfDestroyed();
22818
+ const keySessionContext = this.createMediaKeySessionContext({
22819
+ decryptdata,
22820
+ keySystem,
22821
+ mediaKeys
22822
+ });
22823
+ return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22824
+ });
22825
+ });
22826
+ }
22827
+ keySessionContextPromise.catch(error => this.handleError(error));
22828
+ };
22829
+ this.onWaitingForKey = event => {
22830
+ this.log(`"${event.type}" event`);
22831
+ };
21933
22832
  this.hls = hls;
21934
22833
  this.config = hls.config;
21935
22834
  this.registerListeners();
@@ -21943,9 +22842,9 @@ class EMEController {
21943
22842
  config.licenseXhrSetup = config.licenseResponseCallback = undefined;
21944
22843
  config.drmSystems = config.drmSystemOptions = {};
21945
22844
  // @ts-ignore
21946
- this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
22845
+ this.hls = this.config = this.keyIdToKeySessionPromise = null;
21947
22846
  // @ts-ignore
21948
- this.config = null;
22847
+ this.onMediaEncrypted = this.onWaitingForKey = null;
21949
22848
  }
21950
22849
  registerListeners() {
21951
22850
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -22209,100 +23108,6 @@ class EMEController {
22209
23108
  }
22210
23109
  return this.attemptKeySystemAccess(keySystemsToAttempt);
22211
23110
  }
22212
- _onMediaEncrypted(event) {
22213
- const {
22214
- initDataType,
22215
- initData
22216
- } = event;
22217
- this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22218
-
22219
- // Ignore event when initData is null
22220
- if (initData === null) {
22221
- return;
22222
- }
22223
- let keyId;
22224
- let keySystemDomain;
22225
- if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22226
- // Match sinf keyId to playlist skd://keyId=
22227
- const json = bin2str(new Uint8Array(initData));
22228
- try {
22229
- const sinf = base64Decode(JSON.parse(json).sinf);
22230
- const tenc = parseSinf(new Uint8Array(sinf));
22231
- if (!tenc) {
22232
- return;
22233
- }
22234
- keyId = tenc.subarray(8, 24);
22235
- keySystemDomain = KeySystems.FAIRPLAY;
22236
- } catch (error) {
22237
- this.warn('Failed to parse sinf "encrypted" event message initData');
22238
- return;
22239
- }
22240
- } else {
22241
- // Support clear-lead key-session creation (otherwise depend on playlist keys)
22242
- const psshInfo = parsePssh(initData);
22243
- if (psshInfo === null) {
22244
- return;
22245
- }
22246
- if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22247
- keyId = psshInfo.data.subarray(8, 24);
22248
- }
22249
- keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22250
- }
22251
- if (!keySystemDomain || !keyId) {
22252
- return;
22253
- }
22254
- const keyIdHex = Hex.hexDump(keyId);
22255
- const {
22256
- keyIdToKeySessionPromise,
22257
- mediaKeySessions
22258
- } = this;
22259
- let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22260
- for (let i = 0; i < mediaKeySessions.length; i++) {
22261
- // Match playlist key
22262
- const keyContext = mediaKeySessions[i];
22263
- const decryptdata = keyContext.decryptdata;
22264
- if (decryptdata.pssh || !decryptdata.keyId) {
22265
- continue;
22266
- }
22267
- const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22268
- if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22269
- keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22270
- delete keyIdToKeySessionPromise[oldKeyIdHex];
22271
- decryptdata.pssh = new Uint8Array(initData);
22272
- decryptdata.keyId = keyId;
22273
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22274
- return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22275
- });
22276
- break;
22277
- }
22278
- }
22279
- if (!keySessionContextPromise) {
22280
- // Clear-lead key (not encountered in playlist)
22281
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22282
- keySystem,
22283
- mediaKeys
22284
- }) => {
22285
- var _keySystemToKeySystem;
22286
- this.throwIfDestroyed();
22287
- const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22288
- decryptdata.pssh = new Uint8Array(initData);
22289
- decryptdata.keyId = keyId;
22290
- return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22291
- this.throwIfDestroyed();
22292
- const keySessionContext = this.createMediaKeySessionContext({
22293
- decryptdata,
22294
- keySystem,
22295
- mediaKeys
22296
- });
22297
- return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22298
- });
22299
- });
22300
- }
22301
- keySessionContextPromise.catch(error => this.handleError(error));
22302
- }
22303
- _onWaitingForKey(event) {
22304
- this.log(`"${event.type}" event`);
22305
- }
22306
23111
  attemptSetMediaKeys(keySystem, mediaKeys) {
22307
23112
  const queue = this.setMediaKeysQueue.slice();
22308
23113
  this.log(`Setting media-keys for "${keySystem}"`);
@@ -22895,20 +23700,6 @@ class SfItem {
22895
23700
  }
22896
23701
  }
22897
23702
 
22898
- /**
22899
- * A class to represent structured field tokens when `Symbol` is not available.
22900
- *
22901
- * @group Structured Field
22902
- *
22903
- * @beta
22904
- */
22905
- class SfToken {
22906
- constructor(description) {
22907
- this.description = void 0;
22908
- this.description = description;
22909
- }
22910
- }
22911
-
22912
23703
  const DICT = 'Dict';
22913
23704
 
22914
23705
  function format(value) {
@@ -22932,29 +23723,27 @@ function throwError(action, src, type, cause) {
22932
23723
  });
22933
23724
  }
22934
23725
 
22935
- const BARE_ITEM = 'Bare Item';
22936
-
22937
- const BOOLEAN = 'Boolean';
22938
-
22939
- const BYTES = 'Byte Sequence';
22940
-
22941
- const DECIMAL = 'Decimal';
22942
-
22943
- const INTEGER = 'Integer';
22944
-
22945
- function isInvalidInt(value) {
22946
- return value < -999999999999999 || 999999999999999 < value;
23726
+ function serializeError(src, type, cause) {
23727
+ return throwError('serialize', src, type, cause);
22947
23728
  }
22948
23729
 
22949
- const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
22950
-
22951
- const TOKEN = 'Token';
23730
+ /**
23731
+ * A class to represent structured field tokens when `Symbol` is not available.
23732
+ *
23733
+ * @group Structured Field
23734
+ *
23735
+ * @beta
23736
+ */
23737
+ class SfToken {
23738
+ constructor(description) {
23739
+ this.description = void 0;
23740
+ this.description = description;
23741
+ }
23742
+ }
22952
23743
 
22953
- const KEY = 'Key';
23744
+ const BARE_ITEM = 'Bare Item';
22954
23745
 
22955
- function serializeError(src, type, cause) {
22956
- return throwError('serialize', src, type, cause);
22957
- }
23746
+ const BOOLEAN = 'Boolean';
22958
23747
 
22959
23748
  // 4.1.9. Serializing a Boolean
22960
23749
  //
@@ -22993,6 +23782,8 @@ function base64encode(binary) {
22993
23782
  return btoa(String.fromCharCode(...binary));
22994
23783
  }
22995
23784
 
23785
+ const BYTES = 'Byte Sequence';
23786
+
22996
23787
  // 4.1.8. Serializing a Byte Sequence
22997
23788
  //
22998
23789
  // Given a Byte Sequence as input_bytes, return an ASCII string suitable
@@ -23024,6 +23815,12 @@ function serializeByteSequence(value) {
23024
23815
  return `:${base64encode(value)}:`;
23025
23816
  }
23026
23817
 
23818
+ const INTEGER = 'Integer';
23819
+
23820
+ function isInvalidInt(value) {
23821
+ return value < -999999999999999 || 999999999999999 < value;
23822
+ }
23823
+
23027
23824
  // 4.1.4. Serializing an Integer
23028
23825
  //
23029
23826
  // Given an Integer as input_integer, return an ASCII string suitable
@@ -23089,6 +23886,8 @@ function roundToEven(value, precision) {
23089
23886
  }
23090
23887
  }
23091
23888
 
23889
+ const DECIMAL = 'Decimal';
23890
+
23092
23891
  // 4.1.5. Serializing a Decimal
23093
23892
  //
23094
23893
  // Given a decimal number as input_decimal, return an ASCII string
@@ -23134,6 +23933,8 @@ function serializeDecimal(value) {
23134
23933
 
23135
23934
  const STRING = 'String';
23136
23935
 
23936
+ const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
23937
+
23137
23938
  // 4.1.6. Serializing a String
23138
23939
  //
23139
23940
  // Given a String as input_string, return an ASCII string suitable for
@@ -23169,6 +23970,8 @@ function symbolToStr(symbol) {
23169
23970
  return symbol.description || symbol.toString().slice(7, -1);
23170
23971
  }
23171
23972
 
23973
+ const TOKEN = 'Token';
23974
+
23172
23975
  function serializeToken(token) {
23173
23976
  const value = symbolToStr(token);
23174
23977
  if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
@@ -23236,6 +24039,8 @@ function serializeBareItem(value) {
23236
24039
  }
23237
24040
  }
23238
24041
 
24042
+ const KEY = 'Key';
24043
+
23239
24044
  // 4.1.1.3. Serializing a Key
23240
24045
  //
23241
24046
  // Given a key as input_key, return an ASCII string suitable for use in
@@ -23477,36 +24282,6 @@ function urlToRelativePath(url, base) {
23477
24282
  return toPath.join('/');
23478
24283
  }
23479
24284
 
23480
- /**
23481
- * Generate a random v4 UUID
23482
- *
23483
- * @returns A random v4 UUID
23484
- *
23485
- * @group Utils
23486
- *
23487
- * @beta
23488
- */
23489
- function uuid() {
23490
- try {
23491
- return crypto.randomUUID();
23492
- } catch (error) {
23493
- try {
23494
- const url = URL.createObjectURL(new Blob());
23495
- const uuid = url.toString();
23496
- URL.revokeObjectURL(url);
23497
- return uuid.slice(uuid.lastIndexOf('/') + 1);
23498
- } catch (error) {
23499
- let dt = new Date().getTime();
23500
- const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
23501
- const r = (dt + Math.random() * 16) % 16 | 0;
23502
- dt = Math.floor(dt / 16);
23503
- return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
23504
- });
23505
- return uuid;
23506
- }
23507
- }
23508
- }
23509
-
23510
24285
  const toRounded = value => Math.round(value);
23511
24286
  const toUrlSafe = (value, options) => {
23512
24287
  if (options != null && options.baseUrl) {
@@ -23732,6 +24507,36 @@ function appendCmcdQuery(url, cmcd, options) {
23732
24507
  return `${url}${separator}${query}`;
23733
24508
  }
23734
24509
 
24510
+ /**
24511
+ * Generate a random v4 UUID
24512
+ *
24513
+ * @returns A random v4 UUID
24514
+ *
24515
+ * @group Utils
24516
+ *
24517
+ * @beta
24518
+ */
24519
+ function uuid() {
24520
+ try {
24521
+ return crypto.randomUUID();
24522
+ } catch (error) {
24523
+ try {
24524
+ const url = URL.createObjectURL(new Blob());
24525
+ const uuid = url.toString();
24526
+ URL.revokeObjectURL(url);
24527
+ return uuid.slice(uuid.lastIndexOf('/') + 1);
24528
+ } catch (error) {
24529
+ let dt = new Date().getTime();
24530
+ const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
24531
+ const r = (dt + Math.random() * 16) % 16 | 0;
24532
+ dt = Math.floor(dt / 16);
24533
+ return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
24534
+ });
24535
+ return uuid;
24536
+ }
24537
+ }
24538
+ }
24539
+
23735
24540
  /**
23736
24541
  * Controller to deal with Common Media Client Data (CMCD)
23737
24542
  * @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
@@ -23795,6 +24600,12 @@ class CMCDController {
23795
24600
  data.tb = this.getTopBandwidth(ot) / 1000;
23796
24601
  data.bl = this.getBufferLength(ot);
23797
24602
  }
24603
+ const next = this.getNextFrag(fragment);
24604
+ if (next) {
24605
+ if (next.url && next.url !== fragment.url) {
24606
+ data.nor = next.url;
24607
+ }
24608
+ }
23798
24609
  this.apply(context, data);
23799
24610
  } catch (error) {
23800
24611
  logger.warn('Could not generate segment CMCD data.', error);
@@ -23887,7 +24698,7 @@ class CMCDController {
23887
24698
  data.su = this.buffering;
23888
24699
  }
23889
24700
 
23890
- // TODO: Implement rtp, nrr, nor, dl
24701
+ // TODO: Implement rtp, nrr, dl
23891
24702
 
23892
24703
  const {
23893
24704
  includeKeys
@@ -23898,15 +24709,28 @@ class CMCDController {
23898
24709
  return acc;
23899
24710
  }, {});
23900
24711
  }
24712
+ const options = {
24713
+ baseUrl: context.url
24714
+ };
23901
24715
  if (this.useHeaders) {
23902
24716
  if (!context.headers) {
23903
24717
  context.headers = {};
23904
24718
  }
23905
- appendCmcdHeaders(context.headers, data);
24719
+ appendCmcdHeaders(context.headers, data, options);
23906
24720
  } else {
23907
- context.url = appendCmcdQuery(context.url, data);
24721
+ context.url = appendCmcdQuery(context.url, data, options);
24722
+ }
24723
+ }
24724
+ getNextFrag(fragment) {
24725
+ var _this$hls$levels$frag;
24726
+ const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
24727
+ if (levelDetails) {
24728
+ const index = fragment.sn - levelDetails.startSN;
24729
+ return levelDetails.fragments[index + 1];
23908
24730
  }
24731
+ return undefined;
23909
24732
  }
24733
+
23910
24734
  /**
23911
24735
  * The CMCD object type.
23912
24736
  */
@@ -24035,10 +24859,10 @@ class CMCDController {
24035
24859
  }
24036
24860
 
24037
24861
  const PATHWAY_PENALTY_DURATION_MS = 300000;
24038
- class ContentSteeringController {
24862
+ class ContentSteeringController extends Logger {
24039
24863
  constructor(hls) {
24864
+ super('content-steering', hls.logger);
24040
24865
  this.hls = void 0;
24041
- this.log = void 0;
24042
24866
  this.loader = null;
24043
24867
  this.uri = null;
24044
24868
  this.pathwayId = '.';
@@ -24053,7 +24877,6 @@ class ContentSteeringController {
24053
24877
  this.subtitleTracks = null;
24054
24878
  this.penalizedPathways = {};
24055
24879
  this.hls = hls;
24056
- this.log = logger.log.bind(logger, `[content-steering]:`);
24057
24880
  this.registerListeners();
24058
24881
  }
24059
24882
  registerListeners() {
@@ -24177,7 +25000,7 @@ class ContentSteeringController {
24177
25000
  errorAction.resolved = this.pathwayId !== errorPathway;
24178
25001
  }
24179
25002
  if (!errorAction.resolved) {
24180
- 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)}`);
25003
+ 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)}`);
24181
25004
  }
24182
25005
  }
24183
25006
  }
@@ -24348,7 +25171,7 @@ class ContentSteeringController {
24348
25171
  onSuccess: (response, stats, context, networkDetails) => {
24349
25172
  this.log(`Loaded steering manifest: "${url}"`);
24350
25173
  const steeringData = response.data;
24351
- if (steeringData.VERSION !== 1) {
25174
+ if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
24352
25175
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
24353
25176
  return;
24354
25177
  }
@@ -25318,7 +26141,7 @@ function timelineConfig() {
25318
26141
  /**
25319
26142
  * @ignore
25320
26143
  */
25321
- function mergeConfig(defaultConfig, userConfig) {
26144
+ function mergeConfig(defaultConfig, userConfig, logger) {
25322
26145
  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
25323
26146
  throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
25324
26147
  }
@@ -25388,7 +26211,7 @@ function deepCpy(obj) {
25388
26211
  /**
25389
26212
  * @ignore
25390
26213
  */
25391
- function enableStreamingMode(config) {
26214
+ function enableStreamingMode(config, logger) {
25392
26215
  const currentLoader = config.loader;
25393
26216
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
25394
26217
  // If a developer has configured their own loader, respect that choice
@@ -25405,10 +26228,9 @@ function enableStreamingMode(config) {
25405
26228
  }
25406
26229
  }
25407
26230
 
25408
- let chromeOrFirefox;
25409
26231
  class LevelController extends BasePlaylistController {
25410
26232
  constructor(hls, contentSteeringController) {
25411
- super(hls, '[level-controller]');
26233
+ super(hls, 'level-controller');
25412
26234
  this._levels = [];
25413
26235
  this._firstLevel = -1;
25414
26236
  this._maxAutoLevel = -1;
@@ -25479,23 +26301,15 @@ class LevelController extends BasePlaylistController {
25479
26301
  let videoCodecFound = false;
25480
26302
  let audioCodecFound = false;
25481
26303
  data.levels.forEach(levelParsed => {
25482
- var _audioCodec, _videoCodec;
26304
+ var _videoCodec;
25483
26305
  const attributes = levelParsed.attrs;
25484
-
25485
- // erase audio codec info if browser does not support mp4a.40.34.
25486
- // demuxer will autodetect codec and fallback to mpeg/audio
25487
26306
  let {
25488
26307
  audioCodec,
25489
26308
  videoCodec
25490
26309
  } = levelParsed;
25491
- if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
25492
- chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
25493
- if (chromeOrFirefox) {
25494
- levelParsed.audioCodec = audioCodec = undefined;
25495
- }
25496
- }
25497
26310
  if (audioCodec) {
25498
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
26311
+ // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
26312
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
25499
26313
  }
25500
26314
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
25501
26315
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -26081,6 +26895,8 @@ class KeyLoader {
26081
26895
  }
26082
26896
  return this.loadKeyEME(keyInfo, frag);
26083
26897
  case 'AES-128':
26898
+ case 'AES-256':
26899
+ case 'AES-256-CTR':
26084
26900
  return this.loadKeyHTTP(keyInfo, frag);
26085
26901
  default:
26086
26902
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -26218,8 +27034,9 @@ const STALL_MINIMUM_DURATION_MS = 250;
26218
27034
  const MAX_START_GAP_JUMP = 2.0;
26219
27035
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
26220
27036
  const SKIP_BUFFER_RANGE_START = 0.05;
26221
- class GapController {
27037
+ class GapController extends Logger {
26222
27038
  constructor(config, media, fragmentTracker, hls) {
27039
+ super('gap-controller', hls.logger);
26223
27040
  this.config = void 0;
26224
27041
  this.media = null;
26225
27042
  this.fragmentTracker = void 0;
@@ -26229,6 +27046,7 @@ class GapController {
26229
27046
  this.stalled = null;
26230
27047
  this.moved = false;
26231
27048
  this.seeking = false;
27049
+ this.ended = 0;
26232
27050
  this.config = config;
26233
27051
  this.media = media;
26234
27052
  this.fragmentTracker = fragmentTracker;
@@ -26246,7 +27064,7 @@ class GapController {
26246
27064
  *
26247
27065
  * @param lastCurrentTime - Previously read playhead position
26248
27066
  */
26249
- poll(lastCurrentTime, activeFrag) {
27067
+ poll(lastCurrentTime, activeFrag, levelDetails, state) {
26250
27068
  const {
26251
27069
  config,
26252
27070
  media,
@@ -26265,6 +27083,7 @@ class GapController {
26265
27083
 
26266
27084
  // The playhead is moving, no-op
26267
27085
  if (currentTime !== lastCurrentTime) {
27086
+ this.ended = 0;
26268
27087
  this.moved = true;
26269
27088
  if (!seeking) {
26270
27089
  this.nudgeRetry = 0;
@@ -26273,7 +27092,7 @@ class GapController {
26273
27092
  // The playhead is now moving, but was previously stalled
26274
27093
  if (this.stallReported) {
26275
27094
  const _stalledDuration = self.performance.now() - stalled;
26276
- logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
27095
+ this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
26277
27096
  this.stallReported = false;
26278
27097
  }
26279
27098
  this.stalled = null;
@@ -26309,7 +27128,6 @@ class GapController {
26309
27128
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
26310
27129
  // The addition poll gives the browser a chance to jump the gap for us
26311
27130
  if (!this.moved && this.stalled !== null) {
26312
- var _level$details;
26313
27131
  // There is no playable buffer (seeked, waiting for buffer)
26314
27132
  const isBuffered = bufferInfo.len > 0;
26315
27133
  if (!isBuffered && !nextStart) {
@@ -26321,9 +27139,8 @@ class GapController {
26321
27139
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
26322
27140
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
26323
27141
  // that begins over 1 target duration after the video start position.
26324
- const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
26325
- const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
26326
- const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
27142
+ const isLive = !!(levelDetails != null && levelDetails.live);
27143
+ const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
26327
27144
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
26328
27145
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
26329
27146
  if (!media.paused) {
@@ -26341,6 +27158,17 @@ class GapController {
26341
27158
  }
26342
27159
  const stalledDuration = tnow - stalled;
26343
27160
  if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
27161
+ // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
27162
+ if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
27163
+ if (stalledDuration < 1000 || this.ended) {
27164
+ return;
27165
+ }
27166
+ this.ended = currentTime;
27167
+ this.hls.trigger(Events.MEDIA_ENDED, {
27168
+ stalled: true
27169
+ });
27170
+ return;
27171
+ }
26344
27172
  // Report stalling after trying to fix
26345
27173
  this._reportStall(bufferInfo);
26346
27174
  if (!this.media) {
@@ -26384,7 +27212,7 @@ class GapController {
26384
27212
  // needs to cross some sort of threshold covering all source-buffers content
26385
27213
  // to start playing properly.
26386
27214
  if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
26387
- logger.warn('Trying to nudge playhead over buffer-hole');
27215
+ this.warn('Trying to nudge playhead over buffer-hole');
26388
27216
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
26389
27217
  // We only try to jump the hole if it's under the configured size
26390
27218
  // Reset stalled so to rearm watchdog timer
@@ -26408,7 +27236,7 @@ class GapController {
26408
27236
  // Report stalled error once
26409
27237
  this.stallReported = true;
26410
27238
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
26411
- logger.warn(error.message);
27239
+ this.warn(error.message);
26412
27240
  hls.trigger(Events.ERROR, {
26413
27241
  type: ErrorTypes.MEDIA_ERROR,
26414
27242
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -26476,7 +27304,7 @@ class GapController {
26476
27304
  }
26477
27305
  }
26478
27306
  const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
26479
- logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
27307
+ this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
26480
27308
  this.moved = true;
26481
27309
  this.stalled = null;
26482
27310
  media.currentTime = targetTime;
@@ -26517,7 +27345,7 @@ class GapController {
26517
27345
  const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
26518
27346
  // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
26519
27347
  const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
26520
- logger.warn(error.message);
27348
+ this.warn(error.message);
26521
27349
  media.currentTime = targetTime;
26522
27350
  hls.trigger(Events.ERROR, {
26523
27351
  type: ErrorTypes.MEDIA_ERROR,
@@ -26527,7 +27355,7 @@ class GapController {
26527
27355
  });
26528
27356
  } else {
26529
27357
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
26530
- logger.error(error.message);
27358
+ this.error(error.message);
26531
27359
  hls.trigger(Events.ERROR, {
26532
27360
  type: ErrorTypes.MEDIA_ERROR,
26533
27361
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -26542,7 +27370,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
26542
27370
 
26543
27371
  class StreamController extends BaseStreamController {
26544
27372
  constructor(hls, fragmentTracker, keyLoader) {
26545
- super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
27373
+ super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
26546
27374
  this.audioCodecSwap = false;
26547
27375
  this.gapController = null;
26548
27376
  this.level = -1;
@@ -26550,27 +27378,43 @@ class StreamController extends BaseStreamController {
26550
27378
  this.altAudio = false;
26551
27379
  this.audioOnly = false;
26552
27380
  this.fragPlaying = null;
26553
- this.onvplaying = null;
26554
- this.onvseeked = null;
26555
27381
  this.fragLastKbps = 0;
26556
27382
  this.couldBacktrack = false;
26557
27383
  this.backtrackFragment = null;
26558
27384
  this.audioCodecSwitch = false;
26559
27385
  this.videoBuffer = null;
26560
- this._registerListeners();
27386
+ this.onMediaPlaying = () => {
27387
+ // tick to speed up FRAG_CHANGED triggering
27388
+ this.tick();
27389
+ };
27390
+ this.onMediaSeeked = () => {
27391
+ const media = this.media;
27392
+ const currentTime = media ? media.currentTime : null;
27393
+ if (isFiniteNumber(currentTime)) {
27394
+ this.log(`Media seeked to ${currentTime.toFixed(3)}`);
27395
+ }
27396
+
27397
+ // If seeked was issued before buffer was appended do not tick immediately
27398
+ const bufferInfo = this.getMainFwdBufferInfo();
27399
+ if (bufferInfo === null || bufferInfo.len === 0) {
27400
+ this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
27401
+ return;
27402
+ }
27403
+
27404
+ // tick to speed up FRAG_CHANGED triggering
27405
+ this.tick();
27406
+ };
27407
+ this.registerListeners();
26561
27408
  }
26562
- _registerListeners() {
27409
+ registerListeners() {
27410
+ super.registerListeners();
26563
27411
  const {
26564
27412
  hls
26565
27413
  } = this;
26566
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
26567
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
26568
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
26569
27414
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
26570
27415
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
26571
27416
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
26572
27417
  hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26573
- hls.on(Events.ERROR, this.onError, this);
26574
27418
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
26575
27419
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
26576
27420
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -26578,17 +27422,14 @@ class StreamController extends BaseStreamController {
26578
27422
  hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
26579
27423
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
26580
27424
  }
26581
- _unregisterListeners() {
27425
+ unregisterListeners() {
27426
+ super.unregisterListeners();
26582
27427
  const {
26583
27428
  hls
26584
27429
  } = this;
26585
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
26586
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
26587
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
26588
27430
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
26589
27431
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
26590
27432
  hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26591
- hls.off(Events.ERROR, this.onError, this);
26592
27433
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
26593
27434
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
26594
27435
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -26597,7 +27438,9 @@ class StreamController extends BaseStreamController {
26597
27438
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
26598
27439
  }
26599
27440
  onHandlerDestroying() {
26600
- this._unregisterListeners();
27441
+ // @ts-ignore
27442
+ this.onMediaPlaying = this.onMediaSeeked = null;
27443
+ this.unregisterListeners();
26601
27444
  super.onHandlerDestroying();
26602
27445
  }
26603
27446
  startLoad(startPosition) {
@@ -26717,7 +27560,7 @@ class StreamController extends BaseStreamController {
26717
27560
  if (this.altAudio && this.audioOnly) {
26718
27561
  return;
26719
27562
  }
26720
- if (!(levels != null && levels[level])) {
27563
+ if (!this.buffering || !(levels != null && levels[level])) {
26721
27564
  return;
26722
27565
  }
26723
27566
  const levelInfo = levels[level];
@@ -26925,20 +27768,17 @@ class StreamController extends BaseStreamController {
26925
27768
  onMediaAttached(event, data) {
26926
27769
  super.onMediaAttached(event, data);
26927
27770
  const media = data.media;
26928
- this.onvplaying = this.onMediaPlaying.bind(this);
26929
- this.onvseeked = this.onMediaSeeked.bind(this);
26930
- media.addEventListener('playing', this.onvplaying);
26931
- media.addEventListener('seeked', this.onvseeked);
27771
+ media.addEventListener('playing', this.onMediaPlaying);
27772
+ media.addEventListener('seeked', this.onMediaSeeked);
26932
27773
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
26933
27774
  }
26934
27775
  onMediaDetaching() {
26935
27776
  const {
26936
27777
  media
26937
27778
  } = this;
26938
- if (media && this.onvplaying && this.onvseeked) {
26939
- media.removeEventListener('playing', this.onvplaying);
26940
- media.removeEventListener('seeked', this.onvseeked);
26941
- this.onvplaying = this.onvseeked = null;
27779
+ if (media) {
27780
+ media.removeEventListener('playing', this.onMediaPlaying);
27781
+ media.removeEventListener('seeked', this.onMediaSeeked);
26942
27782
  this.videoBuffer = null;
26943
27783
  }
26944
27784
  this.fragPlaying = null;
@@ -26948,27 +27788,6 @@ class StreamController extends BaseStreamController {
26948
27788
  }
26949
27789
  super.onMediaDetaching();
26950
27790
  }
26951
- onMediaPlaying() {
26952
- // tick to speed up FRAG_CHANGED triggering
26953
- this.tick();
26954
- }
26955
- onMediaSeeked() {
26956
- const media = this.media;
26957
- const currentTime = media ? media.currentTime : null;
26958
- if (isFiniteNumber(currentTime)) {
26959
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
26960
- }
26961
-
26962
- // If seeked was issued before buffer was appended do not tick immediately
26963
- const bufferInfo = this.getMainFwdBufferInfo();
26964
- if (bufferInfo === null || bufferInfo.len === 0) {
26965
- this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
26966
- return;
26967
- }
26968
-
26969
- // tick to speed up FRAG_CHANGED triggering
26970
- this.tick();
26971
- }
26972
27791
  onManifestLoading() {
26973
27792
  // reset buffer on manifest loading
26974
27793
  this.log('Trigger BUFFER_RESET');
@@ -27260,8 +28079,10 @@ class StreamController extends BaseStreamController {
27260
28079
  }
27261
28080
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
27262
28081
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
27263
- const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
27264
- gapController.poll(this.lastCurrentTime, activeFrag);
28082
+ const state = this.state;
28083
+ const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
28084
+ const levelDetails = this.getLevelDetails();
28085
+ gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
27265
28086
  }
27266
28087
  this.lastCurrentTime = media.currentTime;
27267
28088
  }
@@ -27699,7 +28520,7 @@ class Hls {
27699
28520
  * Get the video-dev/hls.js package version.
27700
28521
  */
27701
28522
  static get version() {
27702
- return "1.5.4";
28523
+ return "1.5.5-0.canary.9977";
27703
28524
  }
27704
28525
 
27705
28526
  /**
@@ -27762,9 +28583,12 @@ class Hls {
27762
28583
  * The configuration object provided on player instantiation.
27763
28584
  */
27764
28585
  this.userConfig = void 0;
28586
+ /**
28587
+ * The logger functions used by this player instance, configured on player instantiation.
28588
+ */
28589
+ this.logger = void 0;
27765
28590
  this.coreComponents = void 0;
27766
28591
  this.networkControllers = void 0;
27767
- this.started = false;
27768
28592
  this._emitter = new EventEmitter();
27769
28593
  this._autoLevelCapping = -1;
27770
28594
  this._maxHdcpLevel = null;
@@ -27781,11 +28605,11 @@ class Hls {
27781
28605
  this._media = null;
27782
28606
  this.url = null;
27783
28607
  this.triggeringException = void 0;
27784
- enableLogs(userConfig.debug || false, 'Hls instance');
27785
- const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
28608
+ const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
28609
+ const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
27786
28610
  this.userConfig = userConfig;
27787
28611
  if (config.progressive) {
27788
- enableStreamingMode(config);
28612
+ enableStreamingMode(config, logger);
27789
28613
  }
27790
28614
 
27791
28615
  // core controllers and network loaders
@@ -27884,7 +28708,7 @@ class Hls {
27884
28708
  try {
27885
28709
  return this.emit(event, event, eventObject);
27886
28710
  } catch (error) {
27887
- logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
28711
+ this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
27888
28712
  // Prevent recursion in error event handlers that throw #5497
27889
28713
  if (!this.triggeringException) {
27890
28714
  this.triggeringException = true;
@@ -27910,7 +28734,7 @@ class Hls {
27910
28734
  * Dispose of the instance
27911
28735
  */
27912
28736
  destroy() {
27913
- logger.log('destroy');
28737
+ this.logger.log('destroy');
27914
28738
  this.trigger(Events.DESTROYING, undefined);
27915
28739
  this.detachMedia();
27916
28740
  this.removeAllListeners();
@@ -27931,7 +28755,7 @@ class Hls {
27931
28755
  * Attaches Hls.js to a media element
27932
28756
  */
27933
28757
  attachMedia(media) {
27934
- logger.log('attachMedia');
28758
+ this.logger.log('attachMedia');
27935
28759
  this._media = media;
27936
28760
  this.trigger(Events.MEDIA_ATTACHING, {
27937
28761
  media: media
@@ -27942,7 +28766,7 @@ class Hls {
27942
28766
  * Detach Hls.js from the media
27943
28767
  */
27944
28768
  detachMedia() {
27945
- logger.log('detachMedia');
28769
+ this.logger.log('detachMedia');
27946
28770
  this.trigger(Events.MEDIA_DETACHING, undefined);
27947
28771
  this._media = null;
27948
28772
  }
@@ -27959,7 +28783,7 @@ class Hls {
27959
28783
  });
27960
28784
  this._autoLevelCapping = -1;
27961
28785
  this._maxHdcpLevel = null;
27962
- logger.log(`loadSource:${loadingSource}`);
28786
+ this.logger.log(`loadSource:${loadingSource}`);
27963
28787
  if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
27964
28788
  this.detachMedia();
27965
28789
  this.attachMedia(media);
@@ -27978,8 +28802,7 @@ class Hls {
27978
28802
  * Defaults to -1 (None: starts from earliest point)
27979
28803
  */
27980
28804
  startLoad(startPosition = -1) {
27981
- logger.log(`startLoad(${startPosition})`);
27982
- this.started = true;
28805
+ this.logger.log(`startLoad(${startPosition})`);
27983
28806
  this.networkControllers.forEach(controller => {
27984
28807
  controller.startLoad(startPosition);
27985
28808
  });
@@ -27989,34 +28812,31 @@ class Hls {
27989
28812
  * Stop loading of any stream data.
27990
28813
  */
27991
28814
  stopLoad() {
27992
- logger.log('stopLoad');
27993
- this.started = false;
28815
+ this.logger.log('stopLoad');
27994
28816
  this.networkControllers.forEach(controller => {
27995
28817
  controller.stopLoad();
27996
28818
  });
27997
28819
  }
27998
28820
 
27999
28821
  /**
28000
- * Resumes stream controller segment loading if previously started.
28822
+ * Resumes stream controller segment loading after `pauseBuffering` has been called.
28001
28823
  */
28002
28824
  resumeBuffering() {
28003
- if (this.started) {
28004
- this.networkControllers.forEach(controller => {
28005
- if ('fragmentLoader' in controller) {
28006
- controller.startLoad(-1);
28007
- }
28008
- });
28009
- }
28825
+ this.networkControllers.forEach(controller => {
28826
+ if (controller.resumeBuffering) {
28827
+ controller.resumeBuffering();
28828
+ }
28829
+ });
28010
28830
  }
28011
28831
 
28012
28832
  /**
28013
- * Stops stream controller segment loading without changing 'started' state like stopLoad().
28833
+ * Prevents stream controller from loading new segments until `resumeBuffering` is called.
28014
28834
  * This allows for media buffering to be paused without interupting playlist loading.
28015
28835
  */
28016
28836
  pauseBuffering() {
28017
28837
  this.networkControllers.forEach(controller => {
28018
- if ('fragmentLoader' in controller) {
28019
- controller.stopLoad();
28838
+ if (controller.pauseBuffering) {
28839
+ controller.pauseBuffering();
28020
28840
  }
28021
28841
  });
28022
28842
  }
@@ -28025,7 +28845,7 @@ class Hls {
28025
28845
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
28026
28846
  */
28027
28847
  swapAudioCodec() {
28028
- logger.log('swapAudioCodec');
28848
+ this.logger.log('swapAudioCodec');
28029
28849
  this.streamController.swapAudioCodec();
28030
28850
  }
28031
28851
 
@@ -28036,7 +28856,7 @@ class Hls {
28036
28856
  * Automatic recovery of media-errors by this process is configurable.
28037
28857
  */
28038
28858
  recoverMediaError() {
28039
- logger.log('recoverMediaError');
28859
+ this.logger.log('recoverMediaError');
28040
28860
  const media = this._media;
28041
28861
  this.detachMedia();
28042
28862
  if (media) {
@@ -28066,7 +28886,7 @@ class Hls {
28066
28886
  * 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.
28067
28887
  */
28068
28888
  set currentLevel(newLevel) {
28069
- logger.log(`set currentLevel:${newLevel}`);
28889
+ this.logger.log(`set currentLevel:${newLevel}`);
28070
28890
  this.levelController.manualLevel = newLevel;
28071
28891
  this.streamController.immediateLevelSwitch();
28072
28892
  }
@@ -28085,7 +28905,7 @@ class Hls {
28085
28905
  * @param newLevel - Pass -1 for automatic level selection
28086
28906
  */
28087
28907
  set nextLevel(newLevel) {
28088
- logger.log(`set nextLevel:${newLevel}`);
28908
+ this.logger.log(`set nextLevel:${newLevel}`);
28089
28909
  this.levelController.manualLevel = newLevel;
28090
28910
  this.streamController.nextLevelSwitch();
28091
28911
  }
@@ -28104,7 +28924,7 @@ class Hls {
28104
28924
  * @param newLevel - Pass -1 for automatic level selection
28105
28925
  */
28106
28926
  set loadLevel(newLevel) {
28107
- logger.log(`set loadLevel:${newLevel}`);
28927
+ this.logger.log(`set loadLevel:${newLevel}`);
28108
28928
  this.levelController.manualLevel = newLevel;
28109
28929
  }
28110
28930
 
@@ -28135,7 +28955,7 @@ class Hls {
28135
28955
  * Sets "first-level", see getter.
28136
28956
  */
28137
28957
  set firstLevel(newLevel) {
28138
- logger.log(`set firstLevel:${newLevel}`);
28958
+ this.logger.log(`set firstLevel:${newLevel}`);
28139
28959
  this.levelController.firstLevel = newLevel;
28140
28960
  }
28141
28961
 
@@ -28160,7 +28980,7 @@ class Hls {
28160
28980
  * (determined from download of first segment)
28161
28981
  */
28162
28982
  set startLevel(newLevel) {
28163
- logger.log(`set startLevel:${newLevel}`);
28983
+ this.logger.log(`set startLevel:${newLevel}`);
28164
28984
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
28165
28985
  if (newLevel !== -1) {
28166
28986
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -28235,7 +29055,7 @@ class Hls {
28235
29055
  */
28236
29056
  set autoLevelCapping(newLevel) {
28237
29057
  if (this._autoLevelCapping !== newLevel) {
28238
- logger.log(`set autoLevelCapping:${newLevel}`);
29058
+ this.logger.log(`set autoLevelCapping:${newLevel}`);
28239
29059
  this._autoLevelCapping = newLevel;
28240
29060
  this.levelController.checkMaxAutoUpdated();
28241
29061
  }