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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +1 -0
  2. package/dist/hls-demo.js +10 -0
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +1935 -1094
  5. package/dist/hls.js.d.ts +63 -50
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1059 -838
  8. package/dist/hls.light.js.map +1 -1
  9. package/dist/hls.light.min.js +1 -1
  10. package/dist/hls.light.min.js.map +1 -1
  11. package/dist/hls.light.mjs +846 -626
  12. package/dist/hls.light.mjs.map +1 -1
  13. package/dist/hls.min.js +1 -1
  14. package/dist/hls.min.js.map +1 -1
  15. package/dist/hls.mjs +1640 -814
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +18 -18
  20. package/src/config.ts +3 -2
  21. package/src/controller/abr-controller.ts +21 -20
  22. package/src/controller/audio-stream-controller.ts +15 -16
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +7 -7
  25. package/src/controller/base-stream-controller.ts +56 -29
  26. package/src/controller/buffer-controller.ts +11 -11
  27. package/src/controller/cap-level-controller.ts +1 -2
  28. package/src/controller/cmcd-controller.ts +25 -3
  29. package/src/controller/content-steering-controller.ts +8 -6
  30. package/src/controller/eme-controller.ts +9 -22
  31. package/src/controller/error-controller.ts +6 -8
  32. package/src/controller/fps-controller.ts +2 -3
  33. package/src/controller/gap-controller.ts +43 -16
  34. package/src/controller/latency-controller.ts +9 -11
  35. package/src/controller/level-controller.ts +5 -17
  36. package/src/controller/stream-controller.ts +25 -32
  37. package/src/controller/subtitle-stream-controller.ts +13 -14
  38. package/src/controller/subtitle-track-controller.ts +5 -3
  39. package/src/controller/timeline-controller.ts +23 -30
  40. package/src/crypt/aes-crypto.ts +21 -2
  41. package/src/crypt/decrypter-aes-mode.ts +4 -0
  42. package/src/crypt/decrypter.ts +32 -18
  43. package/src/crypt/fast-aes-key.ts +24 -5
  44. package/src/demux/audio/adts.ts +9 -4
  45. package/src/demux/sample-aes.ts +2 -0
  46. package/src/demux/transmuxer-interface.ts +4 -12
  47. package/src/demux/transmuxer-worker.ts +4 -4
  48. package/src/demux/transmuxer.ts +16 -3
  49. package/src/demux/tsdemuxer.ts +71 -37
  50. package/src/demux/video/avc-video-parser.ts +208 -119
  51. package/src/demux/video/base-video-parser.ts +134 -2
  52. package/src/demux/video/exp-golomb.ts +0 -208
  53. package/src/demux/video/hevc-video-parser.ts +746 -0
  54. package/src/events.ts +7 -0
  55. package/src/hls.ts +42 -34
  56. package/src/loader/fragment-loader.ts +9 -2
  57. package/src/loader/key-loader.ts +2 -0
  58. package/src/loader/level-key.ts +10 -9
  59. package/src/remux/mp4-generator.ts +196 -1
  60. package/src/remux/mp4-remuxer.ts +23 -7
  61. package/src/task-loop.ts +5 -2
  62. package/src/types/component-api.ts +2 -0
  63. package/src/types/demuxer.ts +3 -0
  64. package/src/types/events.ts +4 -0
  65. package/src/utils/codecs.ts +33 -4
  66. package/src/utils/encryption-methods-util.ts +21 -0
  67. package/src/utils/logger.ts +53 -24
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.9978"}`);
415
435
  } catch (e) {
416
- exportedLogger = fakeLogger;
436
+ /* log fn threw an exception. All logger methods are no-ops. */
437
+ return createLogger();
417
438
  }
418
- } else {
419
- exportedLogger = fakeLogger;
420
439
  }
440
+ exportedLogger = newLogger;
441
+ return newLogger;
421
442
  }
422
443
  const logger = exportedLogger;
423
444
 
@@ -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
12101
 
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);
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,21 @@ 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
+ {
12897
+ this.videoParser = new HevcVideoParser();
12898
+ }
12899
+ break;
12900
+ }
12901
+ }
12902
+ if (this.videoParser !== null) {
12903
+ this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
12904
+ }
12185
12905
  }
12186
12906
  videoData = {
12187
12907
  data: [],
@@ -12348,8 +13068,22 @@ class TSDemuxer {
12348
13068
  // try to parse last PES packets
12349
13069
  let pes;
12350
13070
  if (videoData && (pes = parsePES(videoData))) {
12351
- this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
12352
- videoTrack.pesData = null;
13071
+ if (this.videoParser === null) {
13072
+ switch (videoTrack.segmentCodec) {
13073
+ case 'avc':
13074
+ this.videoParser = new AvcVideoParser();
13075
+ break;
13076
+ case 'hevc':
13077
+ {
13078
+ this.videoParser = new HevcVideoParser();
13079
+ }
13080
+ break;
13081
+ }
13082
+ }
13083
+ if (this.videoParser !== null) {
13084
+ this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
13085
+ videoTrack.pesData = null;
13086
+ }
12353
13087
  } else {
12354
13088
  // either avcData null or PES truncated, keep it for next frag parsing
12355
13089
  videoTrack.pesData = videoData;
@@ -12682,7 +13416,14 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
12682
13416
  logger.warn('Unsupported EC-3 in M2TS found');
12683
13417
  break;
12684
13418
  case 0x24:
12685
- logger.warn('Unsupported HEVC in M2TS found');
13419
+ // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
13420
+ {
13421
+ if (result.videoPid === -1) {
13422
+ result.videoPid = pid;
13423
+ result.segmentVideoCodec = 'hevc';
13424
+ logger.log('HEVC in M2TS found');
13425
+ }
13426
+ }
12686
13427
  break;
12687
13428
  }
12688
13429
  // move to the next table entry
@@ -12905,6 +13646,8 @@ class MP4 {
12905
13646
  avc1: [],
12906
13647
  // codingname
12907
13648
  avcC: [],
13649
+ hvc1: [],
13650
+ hvcC: [],
12908
13651
  btrt: [],
12909
13652
  dinf: [],
12910
13653
  dref: [],
@@ -13329,8 +14072,10 @@ class MP4 {
13329
14072
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
13330
14073
  }
13331
14074
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
13332
- } else {
14075
+ } else if (track.segmentCodec === 'avc') {
13333
14076
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
14077
+ } else {
14078
+ return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
13334
14079
  }
13335
14080
  }
13336
14081
  static tkhd(track) {
@@ -13468,6 +14213,84 @@ class MP4 {
13468
14213
  const result = appendUint8Array(MP4.FTYP, movie);
13469
14214
  return result;
13470
14215
  }
14216
+ static hvc1(track) {
14217
+ const ps = track.params;
14218
+ const units = [track.vps, track.sps, track.pps];
14219
+ const NALuLengthSize = 4;
14220
+ 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]);
14221
+
14222
+ // compute hvcC size in bytes
14223
+ let length = config.length;
14224
+ for (let i = 0; i < units.length; i += 1) {
14225
+ length += 3;
14226
+ for (let j = 0; j < units[i].length; j += 1) {
14227
+ length += 2 + units[i][j].length;
14228
+ }
14229
+ }
14230
+ const hvcC = new Uint8Array(length);
14231
+ hvcC.set(config, 0);
14232
+ length = config.length;
14233
+ // append parameter set units: one vps, one or more sps and pps
14234
+ const iMax = units.length - 1;
14235
+ for (let i = 0; i < units.length; i += 1) {
14236
+ hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
14237
+ length += 3;
14238
+ for (let j = 0; j < units[i].length; j += 1) {
14239
+ hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
14240
+ length += 2;
14241
+ hvcC.set(units[i][j], length);
14242
+ length += units[i][j].length;
14243
+ }
14244
+ }
14245
+ const hvcc = MP4.box(MP4.types.hvcC, hvcC);
14246
+ const width = track.width;
14247
+ const height = track.height;
14248
+ const hSpacing = track.pixelRatio[0];
14249
+ const vSpacing = track.pixelRatio[1];
14250
+ return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
14251
+ // reserved
14252
+ 0x00, 0x00, 0x00,
14253
+ // reserved
14254
+ 0x00, 0x01,
14255
+ // data_reference_index
14256
+ 0x00, 0x00,
14257
+ // pre_defined
14258
+ 0x00, 0x00,
14259
+ // reserved
14260
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14261
+ // pre_defined
14262
+ width >> 8 & 0xff, width & 0xff,
14263
+ // width
14264
+ height >> 8 & 0xff, height & 0xff,
14265
+ // height
14266
+ 0x00, 0x48, 0x00, 0x00,
14267
+ // horizresolution
14268
+ 0x00, 0x48, 0x00, 0x00,
14269
+ // vertresolution
14270
+ 0x00, 0x00, 0x00, 0x00,
14271
+ // reserved
14272
+ 0x00, 0x01,
14273
+ // frame_count
14274
+ 0x12, 0x64, 0x61, 0x69, 0x6c,
14275
+ // dailymotion/hls.js
14276
+ 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,
14277
+ // compressorname
14278
+ 0x00, 0x18,
14279
+ // depth = 24
14280
+ 0x11, 0x11]),
14281
+ // pre_defined = -1
14282
+ hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
14283
+ // bufferSizeDB
14284
+ 0x00, 0x2d, 0xc6, 0xc0,
14285
+ // maxBitrate
14286
+ 0x00, 0x2d, 0xc6, 0xc0])),
14287
+ // avgBitrate
14288
+ MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
14289
+ // hSpacing
14290
+ hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
14291
+ // vSpacing
14292
+ vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
14293
+ }
13471
14294
  }
13472
14295
  MP4.types = void 0;
13473
14296
  MP4.HDLR_TYPES = void 0;
@@ -13849,9 +14672,9 @@ class MP4Remuxer {
13849
14672
  const foundOverlap = delta < -1;
13850
14673
  if (foundHole || foundOverlap) {
13851
14674
  if (foundHole) {
13852
- logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
14675
+ logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
13853
14676
  } else {
13854
- logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
14677
+ logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
13855
14678
  }
13856
14679
  if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
13857
14680
  firstDTS = nextAvcDts;
@@ -13860,12 +14683,24 @@ class MP4Remuxer {
13860
14683
  inputSamples[0].dts = firstDTS;
13861
14684
  inputSamples[0].pts = firstPTS;
13862
14685
  } else {
14686
+ let isPTSOrderRetained = true;
13863
14687
  for (let i = 0; i < inputSamples.length; i++) {
13864
- if (inputSamples[i].dts > firstPTS) {
14688
+ if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
13865
14689
  break;
13866
14690
  }
14691
+ const prevPTS = inputSamples[i].pts;
13867
14692
  inputSamples[i].dts -= delta;
13868
14693
  inputSamples[i].pts -= delta;
14694
+
14695
+ // check to see if this sample's PTS order has changed
14696
+ // relative to the next one
14697
+ if (i < inputSamples.length - 1) {
14698
+ const nextSamplePTS = inputSamples[i + 1].pts;
14699
+ const currentSamplePTS = inputSamples[i].pts;
14700
+ const currentOrder = nextSamplePTS <= currentSamplePTS;
14701
+ const prevOrder = nextSamplePTS <= prevPTS;
14702
+ isPTSOrderRetained = currentOrder == prevOrder;
14703
+ }
13869
14704
  }
13870
14705
  }
13871
14706
  logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
@@ -14013,7 +14848,7 @@ class MP4Remuxer {
14013
14848
  }
14014
14849
  }
14015
14850
  }
14016
- // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14851
+ // next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14017
14852
  mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
14018
14853
  this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
14019
14854
  this.videoSampleDuration = mp4SampleDuration;
@@ -14146,7 +14981,7 @@ class MP4Remuxer {
14146
14981
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
14147
14982
  for (let j = 0; j < missing; j++) {
14148
14983
  const newStamp = Math.max(nextPts, 0);
14149
- let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
14984
+ let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14150
14985
  if (!fillFrame) {
14151
14986
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
14152
14987
  fillFrame = sample.unit.subarray();
@@ -14274,7 +15109,7 @@ class MP4Remuxer {
14274
15109
  // samples count of this segment's duration
14275
15110
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
14276
15111
  // silent frame
14277
- const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
15112
+ const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14278
15113
  logger.warn('[mp4-remuxer]: remux empty Audio');
14279
15114
  // Can't remux if we can't generate a silent frame...
14280
15115
  if (!silentFrame) {
@@ -14668,13 +15503,15 @@ class Transmuxer {
14668
15503
  initSegmentData
14669
15504
  } = transmuxConfig;
14670
15505
  const keyData = getEncryptionType(uintData, decryptdata);
14671
- if (keyData && keyData.method === 'AES-128') {
15506
+ if (keyData && isFullSegmentEncryption(keyData.method)) {
14672
15507
  const decrypter = this.getDecrypter();
15508
+ const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
15509
+
14673
15510
  // Software decryption is synchronous; webCrypto is not
14674
15511
  if (decrypter.isSync()) {
14675
15512
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
14676
15513
  // data is handled in the flush() call
14677
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
15514
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
14678
15515
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
14679
15516
  const loadingParts = chunkMeta.part > -1;
14680
15517
  if (loadingParts) {
@@ -14686,7 +15523,7 @@ class Transmuxer {
14686
15523
  }
14687
15524
  uintData = new Uint8Array(decryptedData);
14688
15525
  } else {
14689
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
15526
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
14690
15527
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
14691
15528
  // the decrypted data has been transmuxed
14692
15529
  const result = this.push(decryptedData, null, chunkMeta);
@@ -15340,14 +16177,7 @@ class TransmuxerInterface {
15340
16177
  this.observer = new EventEmitter();
15341
16178
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
15342
16179
  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
- };
16180
+ const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
15351
16181
 
15352
16182
  // navigator.vendor is not always available in Web Worker
15353
16183
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -15635,7 +16465,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
15635
16465
 
15636
16466
  class AudioStreamController extends BaseStreamController {
15637
16467
  constructor(hls, fragmentTracker, keyLoader) {
15638
- super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);
16468
+ super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
15639
16469
  this.videoBuffer = null;
15640
16470
  this.videoTrackCC = -1;
15641
16471
  this.waitingVideoCC = -1;
@@ -15647,27 +16477,24 @@ class AudioStreamController extends BaseStreamController {
15647
16477
  this.flushing = false;
15648
16478
  this.bufferFlushed = false;
15649
16479
  this.cachedTrackLoadedData = null;
15650
- this._registerListeners();
16480
+ this.registerListeners();
15651
16481
  }
15652
16482
  onHandlerDestroying() {
15653
- this._unregisterListeners();
16483
+ this.unregisterListeners();
15654
16484
  super.onHandlerDestroying();
15655
16485
  this.mainDetails = null;
15656
16486
  this.bufferedTrack = null;
15657
16487
  this.switchingTrack = null;
15658
16488
  }
15659
- _registerListeners() {
16489
+ registerListeners() {
16490
+ super.registerListeners();
15660
16491
  const {
15661
16492
  hls
15662
16493
  } = 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
16494
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
15667
16495
  hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
15668
16496
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
15669
16497
  hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15670
- hls.on(Events.ERROR, this.onError, this);
15671
16498
  hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
15672
16499
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
15673
16500
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -15675,18 +16502,18 @@ class AudioStreamController extends BaseStreamController {
15675
16502
  hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
15676
16503
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
15677
16504
  }
15678
- _unregisterListeners() {
16505
+ unregisterListeners() {
15679
16506
  const {
15680
16507
  hls
15681
16508
  } = 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);
16509
+ if (!hls) {
16510
+ return;
16511
+ }
16512
+ super.unregisterListeners();
15685
16513
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
15686
16514
  hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
15687
16515
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
15688
16516
  hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15689
- hls.off(Events.ERROR, this.onError, this);
15690
16517
  hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
15691
16518
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
15692
16519
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -15855,12 +16682,13 @@ class AudioStreamController extends BaseStreamController {
15855
16682
  } = this;
15856
16683
  const config = hls.config;
15857
16684
 
15858
- // 1. if video not attached AND
16685
+ // 1. if buffering is suspended
16686
+ // 2. if video not attached AND
15859
16687
  // start fragment already requested OR start frag prefetch not enabled
15860
- // 2. if tracks or track not loaded and selected
16688
+ // 3. if tracks or track not loaded and selected
15861
16689
  // then exit loop
15862
16690
  // => 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])) {
16691
+ if (!this.buffering || !media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
15864
16692
  return;
15865
16693
  }
15866
16694
  const levelInfo = levels[trackId];
@@ -16418,7 +17246,7 @@ class AudioStreamController extends BaseStreamController {
16418
17246
 
16419
17247
  class AudioTrackController extends BasePlaylistController {
16420
17248
  constructor(hls) {
16421
- super(hls, '[audio-track-controller]');
17249
+ super(hls, 'audio-track-controller');
16422
17250
  this.tracks = [];
16423
17251
  this.groupIds = null;
16424
17252
  this.tracksInGroup = [];
@@ -16737,26 +17565,23 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
16737
17565
 
16738
17566
  class SubtitleStreamController extends BaseStreamController {
16739
17567
  constructor(hls, fragmentTracker, keyLoader) {
16740
- super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);
17568
+ super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
16741
17569
  this.currentTrackId = -1;
16742
17570
  this.tracksBuffered = [];
16743
17571
  this.mainDetails = null;
16744
- this._registerListeners();
17572
+ this.registerListeners();
16745
17573
  }
16746
17574
  onHandlerDestroying() {
16747
- this._unregisterListeners();
17575
+ this.unregisterListeners();
16748
17576
  super.onHandlerDestroying();
16749
17577
  this.mainDetails = null;
16750
17578
  }
16751
- _registerListeners() {
17579
+ registerListeners() {
17580
+ super.registerListeners();
16752
17581
  const {
16753
17582
  hls
16754
17583
  } = 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
17584
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16759
- hls.on(Events.ERROR, this.onError, this);
16760
17585
  hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
16761
17586
  hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
16762
17587
  hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -16764,15 +17589,12 @@ class SubtitleStreamController extends BaseStreamController {
16764
17589
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
16765
17590
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
16766
17591
  }
16767
- _unregisterListeners() {
17592
+ unregisterListeners() {
17593
+ super.unregisterListeners();
16768
17594
  const {
16769
17595
  hls
16770
17596
  } = 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
17597
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16775
- hls.off(Events.ERROR, this.onError, this);
16776
17598
  hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
16777
17599
  hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
16778
17600
  hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -16999,10 +17821,10 @@ class SubtitleStreamController extends BaseStreamController {
16999
17821
  return;
17000
17822
  }
17001
17823
  // 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') {
17824
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
17003
17825
  const startTime = performance.now();
17004
17826
  // decrypt the subtitles
17005
- this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
17827
+ this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
17006
17828
  hls.trigger(Events.ERROR, {
17007
17829
  type: ErrorTypes.MEDIA_ERROR,
17008
17830
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -17136,7 +17958,7 @@ class BufferableInstance {
17136
17958
 
17137
17959
  class SubtitleTrackController extends BasePlaylistController {
17138
17960
  constructor(hls) {
17139
- super(hls, '[subtitle-track-controller]');
17961
+ super(hls, 'subtitle-track-controller');
17140
17962
  this.media = null;
17141
17963
  this.tracks = [];
17142
17964
  this.groupIds = null;
@@ -17145,10 +17967,10 @@ class SubtitleTrackController extends BasePlaylistController {
17145
17967
  this.currentTrack = null;
17146
17968
  this.selectDefaultTrack = true;
17147
17969
  this.queuedDefaultTrack = -1;
17148
- this.asyncPollTrackChange = () => this.pollTrackChange(0);
17149
17970
  this.useTextTrackPolling = false;
17150
17971
  this.subtitlePollingInterval = -1;
17151
17972
  this._subtitleDisplay = true;
17973
+ this.asyncPollTrackChange = () => this.pollTrackChange(0);
17152
17974
  this.onTextTracksChanged = () => {
17153
17975
  if (!this.useTextTrackPolling) {
17154
17976
  self.clearInterval(this.subtitlePollingInterval);
@@ -17182,6 +18004,7 @@ class SubtitleTrackController extends BasePlaylistController {
17182
18004
  this.tracks.length = 0;
17183
18005
  this.tracksInGroup.length = 0;
17184
18006
  this.currentTrack = null;
18007
+ // @ts-ignore
17185
18008
  this.onTextTracksChanged = this.asyncPollTrackChange = null;
17186
18009
  super.destroy();
17187
18010
  }
@@ -17642,8 +18465,9 @@ class BufferOperationQueue {
17642
18465
  }
17643
18466
 
17644
18467
  const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
17645
- class BufferController {
18468
+ class BufferController extends Logger {
17646
18469
  constructor(hls) {
18470
+ super('buffer-controller', hls.logger);
17647
18471
  // The level details used to determine duration, target-duration and live
17648
18472
  this.details = null;
17649
18473
  // cache the self generated object url to detect hijack of video tag
@@ -17673,9 +18497,6 @@ class BufferController {
17673
18497
  this.tracks = {};
17674
18498
  this.pendingTracks = {};
17675
18499
  this.sourceBuffer = void 0;
17676
- this.log = void 0;
17677
- this.warn = void 0;
17678
- this.error = void 0;
17679
18500
  this._onEndStreaming = event => {
17680
18501
  if (!this.hls) {
17681
18502
  return;
@@ -17721,15 +18542,11 @@ class BufferController {
17721
18542
  _objectUrl
17722
18543
  } = this;
17723
18544
  if (mediaSrc !== _objectUrl) {
17724
- logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
18545
+ this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
17725
18546
  }
17726
18547
  };
17727
18548
  this.hls = hls;
17728
- const logPrefix = '[buffer-controller]';
17729
18549
  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
18550
  this._initSourceBuffer();
17734
18551
  this.registerListeners();
17735
18552
  }
@@ -17742,6 +18559,12 @@ class BufferController {
17742
18559
  this.lastMpegAudioChunk = null;
17743
18560
  // @ts-ignore
17744
18561
  this.hls = null;
18562
+ // @ts-ignore
18563
+ this._onMediaSourceOpen = this._onMediaSourceClose = null;
18564
+ // @ts-ignore
18565
+ this._onMediaSourceEnded = null;
18566
+ // @ts-ignore
18567
+ this._onStartStreaming = this._onEndStreaming = null;
17745
18568
  }
17746
18569
  registerListeners() {
17747
18570
  const {
@@ -17904,6 +18727,7 @@ class BufferController {
17904
18727
  this.resetBuffer(type);
17905
18728
  });
17906
18729
  this._initSourceBuffer();
18730
+ this.hls.resumeBuffering();
17907
18731
  }
17908
18732
  resetBuffer(type) {
17909
18733
  const sb = this.sourceBuffer[type];
@@ -21006,14 +21830,12 @@ class TimelineController {
21006
21830
  this.cea608Parser1 = this.cea608Parser2 = undefined;
21007
21831
  }
21008
21832
  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
- }
21833
+ const channel1 = new OutputFilter(this, 'textTrack1');
21834
+ const channel2 = new OutputFilter(this, 'textTrack2');
21835
+ const channel3 = new OutputFilter(this, 'textTrack3');
21836
+ const channel4 = new OutputFilter(this, 'textTrack4');
21837
+ this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
21838
+ this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
21017
21839
  }
21018
21840
  addCues(trackName, startTime, endTime, screen, cueRanges) {
21019
21841
  // skip cues which overlap more than 50% with previously parsed time ranges
@@ -21251,7 +22073,7 @@ class TimelineController {
21251
22073
  if (inUseTracks != null && inUseTracks.length) {
21252
22074
  const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
21253
22075
  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.`);
22076
+ 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
22077
  }
21256
22078
  }
21257
22079
  } else if (this.tracks.length) {
@@ -21296,26 +22118,23 @@ class TimelineController {
21296
22118
  return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
21297
22119
  }
21298
22120
  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
22121
  // 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) {
22122
+ if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
21312
22123
  var _data$part$index, _data$part;
22124
+ const {
22125
+ cea608Parser1,
22126
+ cea608Parser2,
22127
+ lastSn
22128
+ } = this;
22129
+ if (!cea608Parser1 || !cea608Parser2) {
22130
+ return;
22131
+ }
21313
22132
  const {
21314
22133
  cc,
21315
22134
  sn
21316
22135
  } = 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)) {
22136
+ const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
22137
+ if (!(sn === lastSn + 1 || sn === lastSn && partIndex === this.lastPartIndex + 1 || cc === this.lastCc)) {
21319
22138
  cea608Parser1.reset();
21320
22139
  cea608Parser2.reset();
21321
22140
  }
@@ -21372,7 +22191,7 @@ class TimelineController {
21372
22191
  frag: frag
21373
22192
  });
21374
22193
  }, error => {
21375
- logger.log(`Failed to parse IMSC1: ${error}`);
22194
+ hls.logger.log(`Failed to parse IMSC1: ${error}`);
21376
22195
  hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
21377
22196
  success: false,
21378
22197
  frag: frag,
@@ -21413,7 +22232,7 @@ class TimelineController {
21413
22232
  this._fallbackToIMSC1(frag, payload);
21414
22233
  }
21415
22234
  // Something went wrong while parsing. Trigger event with success false.
21416
- logger.log(`Failed to parse VTT cue: ${error}`);
22235
+ hls.logger.log(`Failed to parse VTT cue: ${error}`);
21417
22236
  if (missingInitPTS && maxAvCC > frag.cc) {
21418
22237
  return;
21419
22238
  }
@@ -21474,12 +22293,7 @@ class TimelineController {
21474
22293
  this.captionsTracks = {};
21475
22294
  }
21476
22295
  onFragParsingUserdata(event, data) {
21477
- this.initCea608Parsers();
21478
- const {
21479
- cea608Parser1,
21480
- cea608Parser2
21481
- } = this;
21482
- if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
22296
+ if (!this.enabled || !this.config.enableCEA708Captions) {
21483
22297
  return;
21484
22298
  }
21485
22299
  const {
@@ -21494,9 +22308,12 @@ class TimelineController {
21494
22308
  for (let i = 0; i < samples.length; i++) {
21495
22309
  const ccBytes = samples[i].bytes;
21496
22310
  if (ccBytes) {
22311
+ if (!this.cea608Parser1) {
22312
+ this.initCea608Parsers();
22313
+ }
21497
22314
  const ccdatas = this.extractCea608Data(ccBytes);
21498
- cea608Parser1.addData(samples[i].pts, ccdatas[0]);
21499
- cea608Parser2.addData(samples[i].pts, ccdatas[1]);
22315
+ this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
22316
+ this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
21500
22317
  }
21501
22318
  }
21502
22319
  }
@@ -21692,7 +22509,7 @@ class CapLevelController {
21692
22509
  const hls = this.hls;
21693
22510
  const maxLevel = this.getMaxLevel(levels.length - 1);
21694
22511
  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}`);
22512
+ hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
21696
22513
  }
21697
22514
  hls.autoLevelCapping = maxLevel;
21698
22515
  if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
@@ -21870,10 +22687,10 @@ class FPSController {
21870
22687
  totalDroppedFrames: droppedFrames
21871
22688
  });
21872
22689
  if (droppedFPS > 0) {
21873
- // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
22690
+ // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
21874
22691
  if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
21875
22692
  let currentLevel = hls.currentLevel;
21876
- logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
22693
+ hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
21877
22694
  if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
21878
22695
  currentLevel = currentLevel - 1;
21879
22696
  hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
@@ -21905,7 +22722,6 @@ class FPSController {
21905
22722
  }
21906
22723
  }
21907
22724
 
21908
- const LOGGER_PREFIX = '[eme]';
21909
22725
  /**
21910
22726
  * Controller to deal with encrypted media extensions (EME)
21911
22727
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
@@ -21913,8 +22729,9 @@ const LOGGER_PREFIX = '[eme]';
21913
22729
  * @class
21914
22730
  * @constructor
21915
22731
  */
21916
- class EMEController {
22732
+ class EMEController extends Logger {
21917
22733
  constructor(hls) {
22734
+ super('eme', hls.logger);
21918
22735
  this.hls = void 0;
21919
22736
  this.config = void 0;
21920
22737
  this.media = null;
@@ -21924,12 +22741,100 @@ class EMEController {
21924
22741
  this.mediaKeySessions = [];
21925
22742
  this.keyIdToKeySessionPromise = {};
21926
22743
  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);
22744
+ this.onMediaEncrypted = event => {
22745
+ const {
22746
+ initDataType,
22747
+ initData
22748
+ } = event;
22749
+ this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22750
+
22751
+ // Ignore event when initData is null
22752
+ if (initData === null) {
22753
+ return;
22754
+ }
22755
+ let keyId;
22756
+ let keySystemDomain;
22757
+ if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22758
+ // Match sinf keyId to playlist skd://keyId=
22759
+ const json = bin2str(new Uint8Array(initData));
22760
+ try {
22761
+ const sinf = base64Decode(JSON.parse(json).sinf);
22762
+ const tenc = parseSinf(new Uint8Array(sinf));
22763
+ if (!tenc) {
22764
+ return;
22765
+ }
22766
+ keyId = tenc.subarray(8, 24);
22767
+ keySystemDomain = KeySystems.FAIRPLAY;
22768
+ } catch (error) {
22769
+ this.warn('Failed to parse sinf "encrypted" event message initData');
22770
+ return;
22771
+ }
22772
+ } else {
22773
+ // Support clear-lead key-session creation (otherwise depend on playlist keys)
22774
+ const psshInfo = parsePssh(initData);
22775
+ if (psshInfo === null) {
22776
+ return;
22777
+ }
22778
+ if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22779
+ keyId = psshInfo.data.subarray(8, 24);
22780
+ }
22781
+ keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22782
+ }
22783
+ if (!keySystemDomain || !keyId) {
22784
+ return;
22785
+ }
22786
+ const keyIdHex = Hex.hexDump(keyId);
22787
+ const {
22788
+ keyIdToKeySessionPromise,
22789
+ mediaKeySessions
22790
+ } = this;
22791
+ let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22792
+ for (let i = 0; i < mediaKeySessions.length; i++) {
22793
+ // Match playlist key
22794
+ const keyContext = mediaKeySessions[i];
22795
+ const decryptdata = keyContext.decryptdata;
22796
+ if (decryptdata.pssh || !decryptdata.keyId) {
22797
+ continue;
22798
+ }
22799
+ const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22800
+ if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22801
+ keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22802
+ delete keyIdToKeySessionPromise[oldKeyIdHex];
22803
+ decryptdata.pssh = new Uint8Array(initData);
22804
+ decryptdata.keyId = keyId;
22805
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22806
+ return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22807
+ });
22808
+ break;
22809
+ }
22810
+ }
22811
+ if (!keySessionContextPromise) {
22812
+ // Clear-lead key (not encountered in playlist)
22813
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22814
+ keySystem,
22815
+ mediaKeys
22816
+ }) => {
22817
+ var _keySystemToKeySystem;
22818
+ this.throwIfDestroyed();
22819
+ const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22820
+ decryptdata.pssh = new Uint8Array(initData);
22821
+ decryptdata.keyId = keyId;
22822
+ return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22823
+ this.throwIfDestroyed();
22824
+ const keySessionContext = this.createMediaKeySessionContext({
22825
+ decryptdata,
22826
+ keySystem,
22827
+ mediaKeys
22828
+ });
22829
+ return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22830
+ });
22831
+ });
22832
+ }
22833
+ keySessionContextPromise.catch(error => this.handleError(error));
22834
+ };
22835
+ this.onWaitingForKey = event => {
22836
+ this.log(`"${event.type}" event`);
22837
+ };
21933
22838
  this.hls = hls;
21934
22839
  this.config = hls.config;
21935
22840
  this.registerListeners();
@@ -21943,9 +22848,9 @@ class EMEController {
21943
22848
  config.licenseXhrSetup = config.licenseResponseCallback = undefined;
21944
22849
  config.drmSystems = config.drmSystemOptions = {};
21945
22850
  // @ts-ignore
21946
- this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
22851
+ this.hls = this.config = this.keyIdToKeySessionPromise = null;
21947
22852
  // @ts-ignore
21948
- this.config = null;
22853
+ this.onMediaEncrypted = this.onWaitingForKey = null;
21949
22854
  }
21950
22855
  registerListeners() {
21951
22856
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -22209,100 +23114,6 @@ class EMEController {
22209
23114
  }
22210
23115
  return this.attemptKeySystemAccess(keySystemsToAttempt);
22211
23116
  }
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
23117
  attemptSetMediaKeys(keySystem, mediaKeys) {
22307
23118
  const queue = this.setMediaKeysQueue.slice();
22308
23119
  this.log(`Setting media-keys for "${keySystem}"`);
@@ -22895,20 +23706,6 @@ class SfItem {
22895
23706
  }
22896
23707
  }
22897
23708
 
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
23709
  const DICT = 'Dict';
22913
23710
 
22914
23711
  function format(value) {
@@ -22932,29 +23729,27 @@ function throwError(action, src, type, cause) {
22932
23729
  });
22933
23730
  }
22934
23731
 
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;
23732
+ function serializeError(src, type, cause) {
23733
+ return throwError('serialize', src, type, cause);
22947
23734
  }
22948
23735
 
22949
- const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
22950
-
22951
- const TOKEN = 'Token';
23736
+ /**
23737
+ * A class to represent structured field tokens when `Symbol` is not available.
23738
+ *
23739
+ * @group Structured Field
23740
+ *
23741
+ * @beta
23742
+ */
23743
+ class SfToken {
23744
+ constructor(description) {
23745
+ this.description = void 0;
23746
+ this.description = description;
23747
+ }
23748
+ }
22952
23749
 
22953
- const KEY = 'Key';
23750
+ const BARE_ITEM = 'Bare Item';
22954
23751
 
22955
- function serializeError(src, type, cause) {
22956
- return throwError('serialize', src, type, cause);
22957
- }
23752
+ const BOOLEAN = 'Boolean';
22958
23753
 
22959
23754
  // 4.1.9. Serializing a Boolean
22960
23755
  //
@@ -22993,6 +23788,8 @@ function base64encode(binary) {
22993
23788
  return btoa(String.fromCharCode(...binary));
22994
23789
  }
22995
23790
 
23791
+ const BYTES = 'Byte Sequence';
23792
+
22996
23793
  // 4.1.8. Serializing a Byte Sequence
22997
23794
  //
22998
23795
  // Given a Byte Sequence as input_bytes, return an ASCII string suitable
@@ -23024,6 +23821,12 @@ function serializeByteSequence(value) {
23024
23821
  return `:${base64encode(value)}:`;
23025
23822
  }
23026
23823
 
23824
+ const INTEGER = 'Integer';
23825
+
23826
+ function isInvalidInt(value) {
23827
+ return value < -999999999999999 || 999999999999999 < value;
23828
+ }
23829
+
23027
23830
  // 4.1.4. Serializing an Integer
23028
23831
  //
23029
23832
  // Given an Integer as input_integer, return an ASCII string suitable
@@ -23089,6 +23892,8 @@ function roundToEven(value, precision) {
23089
23892
  }
23090
23893
  }
23091
23894
 
23895
+ const DECIMAL = 'Decimal';
23896
+
23092
23897
  // 4.1.5. Serializing a Decimal
23093
23898
  //
23094
23899
  // Given a decimal number as input_decimal, return an ASCII string
@@ -23134,6 +23939,8 @@ function serializeDecimal(value) {
23134
23939
 
23135
23940
  const STRING = 'String';
23136
23941
 
23942
+ const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
23943
+
23137
23944
  // 4.1.6. Serializing a String
23138
23945
  //
23139
23946
  // Given a String as input_string, return an ASCII string suitable for
@@ -23169,6 +23976,8 @@ function symbolToStr(symbol) {
23169
23976
  return symbol.description || symbol.toString().slice(7, -1);
23170
23977
  }
23171
23978
 
23979
+ const TOKEN = 'Token';
23980
+
23172
23981
  function serializeToken(token) {
23173
23982
  const value = symbolToStr(token);
23174
23983
  if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
@@ -23236,6 +24045,8 @@ function serializeBareItem(value) {
23236
24045
  }
23237
24046
  }
23238
24047
 
24048
+ const KEY = 'Key';
24049
+
23239
24050
  // 4.1.1.3. Serializing a Key
23240
24051
  //
23241
24052
  // Given a key as input_key, return an ASCII string suitable for use in
@@ -23477,36 +24288,6 @@ function urlToRelativePath(url, base) {
23477
24288
  return toPath.join('/');
23478
24289
  }
23479
24290
 
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
24291
  const toRounded = value => Math.round(value);
23511
24292
  const toUrlSafe = (value, options) => {
23512
24293
  if (options != null && options.baseUrl) {
@@ -23732,6 +24513,36 @@ function appendCmcdQuery(url, cmcd, options) {
23732
24513
  return `${url}${separator}${query}`;
23733
24514
  }
23734
24515
 
24516
+ /**
24517
+ * Generate a random v4 UUID
24518
+ *
24519
+ * @returns A random v4 UUID
24520
+ *
24521
+ * @group Utils
24522
+ *
24523
+ * @beta
24524
+ */
24525
+ function uuid() {
24526
+ try {
24527
+ return crypto.randomUUID();
24528
+ } catch (error) {
24529
+ try {
24530
+ const url = URL.createObjectURL(new Blob());
24531
+ const uuid = url.toString();
24532
+ URL.revokeObjectURL(url);
24533
+ return uuid.slice(uuid.lastIndexOf('/') + 1);
24534
+ } catch (error) {
24535
+ let dt = new Date().getTime();
24536
+ const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
24537
+ const r = (dt + Math.random() * 16) % 16 | 0;
24538
+ dt = Math.floor(dt / 16);
24539
+ return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
24540
+ });
24541
+ return uuid;
24542
+ }
24543
+ }
24544
+ }
24545
+
23735
24546
  /**
23736
24547
  * Controller to deal with Common Media Client Data (CMCD)
23737
24548
  * @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
@@ -23795,6 +24606,12 @@ class CMCDController {
23795
24606
  data.tb = this.getTopBandwidth(ot) / 1000;
23796
24607
  data.bl = this.getBufferLength(ot);
23797
24608
  }
24609
+ const next = this.getNextFrag(fragment);
24610
+ if (next) {
24611
+ if (next.url && next.url !== fragment.url) {
24612
+ data.nor = next.url;
24613
+ }
24614
+ }
23798
24615
  this.apply(context, data);
23799
24616
  } catch (error) {
23800
24617
  logger.warn('Could not generate segment CMCD data.', error);
@@ -23887,7 +24704,7 @@ class CMCDController {
23887
24704
  data.su = this.buffering;
23888
24705
  }
23889
24706
 
23890
- // TODO: Implement rtp, nrr, nor, dl
24707
+ // TODO: Implement rtp, nrr, dl
23891
24708
 
23892
24709
  const {
23893
24710
  includeKeys
@@ -23898,15 +24715,28 @@ class CMCDController {
23898
24715
  return acc;
23899
24716
  }, {});
23900
24717
  }
24718
+ const options = {
24719
+ baseUrl: context.url
24720
+ };
23901
24721
  if (this.useHeaders) {
23902
24722
  if (!context.headers) {
23903
24723
  context.headers = {};
23904
24724
  }
23905
- appendCmcdHeaders(context.headers, data);
24725
+ appendCmcdHeaders(context.headers, data, options);
23906
24726
  } else {
23907
- context.url = appendCmcdQuery(context.url, data);
24727
+ context.url = appendCmcdQuery(context.url, data, options);
24728
+ }
24729
+ }
24730
+ getNextFrag(fragment) {
24731
+ var _this$hls$levels$frag;
24732
+ const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
24733
+ if (levelDetails) {
24734
+ const index = fragment.sn - levelDetails.startSN;
24735
+ return levelDetails.fragments[index + 1];
23908
24736
  }
24737
+ return undefined;
23909
24738
  }
24739
+
23910
24740
  /**
23911
24741
  * The CMCD object type.
23912
24742
  */
@@ -24035,10 +24865,10 @@ class CMCDController {
24035
24865
  }
24036
24866
 
24037
24867
  const PATHWAY_PENALTY_DURATION_MS = 300000;
24038
- class ContentSteeringController {
24868
+ class ContentSteeringController extends Logger {
24039
24869
  constructor(hls) {
24870
+ super('content-steering', hls.logger);
24040
24871
  this.hls = void 0;
24041
- this.log = void 0;
24042
24872
  this.loader = null;
24043
24873
  this.uri = null;
24044
24874
  this.pathwayId = '.';
@@ -24053,7 +24883,6 @@ class ContentSteeringController {
24053
24883
  this.subtitleTracks = null;
24054
24884
  this.penalizedPathways = {};
24055
24885
  this.hls = hls;
24056
- this.log = logger.log.bind(logger, `[content-steering]:`);
24057
24886
  this.registerListeners();
24058
24887
  }
24059
24888
  registerListeners() {
@@ -24177,7 +25006,7 @@ class ContentSteeringController {
24177
25006
  errorAction.resolved = this.pathwayId !== errorPathway;
24178
25007
  }
24179
25008
  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)}`);
25009
+ 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
25010
  }
24182
25011
  }
24183
25012
  }
@@ -24348,7 +25177,7 @@ class ContentSteeringController {
24348
25177
  onSuccess: (response, stats, context, networkDetails) => {
24349
25178
  this.log(`Loaded steering manifest: "${url}"`);
24350
25179
  const steeringData = response.data;
24351
- if (steeringData.VERSION !== 1) {
25180
+ if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
24352
25181
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
24353
25182
  return;
24354
25183
  }
@@ -25318,7 +26147,7 @@ function timelineConfig() {
25318
26147
  /**
25319
26148
  * @ignore
25320
26149
  */
25321
- function mergeConfig(defaultConfig, userConfig) {
26150
+ function mergeConfig(defaultConfig, userConfig, logger) {
25322
26151
  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
25323
26152
  throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
25324
26153
  }
@@ -25388,7 +26217,7 @@ function deepCpy(obj) {
25388
26217
  /**
25389
26218
  * @ignore
25390
26219
  */
25391
- function enableStreamingMode(config) {
26220
+ function enableStreamingMode(config, logger) {
25392
26221
  const currentLoader = config.loader;
25393
26222
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
25394
26223
  // If a developer has configured their own loader, respect that choice
@@ -25405,10 +26234,9 @@ function enableStreamingMode(config) {
25405
26234
  }
25406
26235
  }
25407
26236
 
25408
- let chromeOrFirefox;
25409
26237
  class LevelController extends BasePlaylistController {
25410
26238
  constructor(hls, contentSteeringController) {
25411
- super(hls, '[level-controller]');
26239
+ super(hls, 'level-controller');
25412
26240
  this._levels = [];
25413
26241
  this._firstLevel = -1;
25414
26242
  this._maxAutoLevel = -1;
@@ -25479,23 +26307,15 @@ class LevelController extends BasePlaylistController {
25479
26307
  let videoCodecFound = false;
25480
26308
  let audioCodecFound = false;
25481
26309
  data.levels.forEach(levelParsed => {
25482
- var _audioCodec, _videoCodec;
26310
+ var _videoCodec;
25483
26311
  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
26312
  let {
25488
26313
  audioCodec,
25489
26314
  videoCodec
25490
26315
  } = 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
26316
  if (audioCodec) {
25498
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
26317
+ // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
26318
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
25499
26319
  }
25500
26320
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
25501
26321
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -26081,6 +26901,8 @@ class KeyLoader {
26081
26901
  }
26082
26902
  return this.loadKeyEME(keyInfo, frag);
26083
26903
  case 'AES-128':
26904
+ case 'AES-256':
26905
+ case 'AES-256-CTR':
26084
26906
  return this.loadKeyHTTP(keyInfo, frag);
26085
26907
  default:
26086
26908
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -26218,8 +27040,9 @@ const STALL_MINIMUM_DURATION_MS = 250;
26218
27040
  const MAX_START_GAP_JUMP = 2.0;
26219
27041
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
26220
27042
  const SKIP_BUFFER_RANGE_START = 0.05;
26221
- class GapController {
27043
+ class GapController extends Logger {
26222
27044
  constructor(config, media, fragmentTracker, hls) {
27045
+ super('gap-controller', hls.logger);
26223
27046
  this.config = void 0;
26224
27047
  this.media = null;
26225
27048
  this.fragmentTracker = void 0;
@@ -26229,6 +27052,7 @@ class GapController {
26229
27052
  this.stalled = null;
26230
27053
  this.moved = false;
26231
27054
  this.seeking = false;
27055
+ this.ended = 0;
26232
27056
  this.config = config;
26233
27057
  this.media = media;
26234
27058
  this.fragmentTracker = fragmentTracker;
@@ -26246,7 +27070,7 @@ class GapController {
26246
27070
  *
26247
27071
  * @param lastCurrentTime - Previously read playhead position
26248
27072
  */
26249
- poll(lastCurrentTime, activeFrag) {
27073
+ poll(lastCurrentTime, activeFrag, levelDetails, state) {
26250
27074
  const {
26251
27075
  config,
26252
27076
  media,
@@ -26265,6 +27089,7 @@ class GapController {
26265
27089
 
26266
27090
  // The playhead is moving, no-op
26267
27091
  if (currentTime !== lastCurrentTime) {
27092
+ this.ended = 0;
26268
27093
  this.moved = true;
26269
27094
  if (!seeking) {
26270
27095
  this.nudgeRetry = 0;
@@ -26273,7 +27098,7 @@ class GapController {
26273
27098
  // The playhead is now moving, but was previously stalled
26274
27099
  if (this.stallReported) {
26275
27100
  const _stalledDuration = self.performance.now() - stalled;
26276
- logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
27101
+ this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
26277
27102
  this.stallReported = false;
26278
27103
  }
26279
27104
  this.stalled = null;
@@ -26309,7 +27134,6 @@ class GapController {
26309
27134
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
26310
27135
  // The addition poll gives the browser a chance to jump the gap for us
26311
27136
  if (!this.moved && this.stalled !== null) {
26312
- var _level$details;
26313
27137
  // There is no playable buffer (seeked, waiting for buffer)
26314
27138
  const isBuffered = bufferInfo.len > 0;
26315
27139
  if (!isBuffered && !nextStart) {
@@ -26321,9 +27145,8 @@ class GapController {
26321
27145
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
26322
27146
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
26323
27147
  // 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;
27148
+ const isLive = !!(levelDetails != null && levelDetails.live);
27149
+ const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
26327
27150
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
26328
27151
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
26329
27152
  if (!media.paused) {
@@ -26341,6 +27164,17 @@ class GapController {
26341
27164
  }
26342
27165
  const stalledDuration = tnow - stalled;
26343
27166
  if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
27167
+ // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
27168
+ if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
27169
+ if (stalledDuration < 1000 || this.ended) {
27170
+ return;
27171
+ }
27172
+ this.ended = currentTime;
27173
+ this.hls.trigger(Events.MEDIA_ENDED, {
27174
+ stalled: true
27175
+ });
27176
+ return;
27177
+ }
26344
27178
  // Report stalling after trying to fix
26345
27179
  this._reportStall(bufferInfo);
26346
27180
  if (!this.media) {
@@ -26384,7 +27218,7 @@ class GapController {
26384
27218
  // needs to cross some sort of threshold covering all source-buffers content
26385
27219
  // to start playing properly.
26386
27220
  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');
27221
+ this.warn('Trying to nudge playhead over buffer-hole');
26388
27222
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
26389
27223
  // We only try to jump the hole if it's under the configured size
26390
27224
  // Reset stalled so to rearm watchdog timer
@@ -26408,7 +27242,7 @@ class GapController {
26408
27242
  // Report stalled error once
26409
27243
  this.stallReported = true;
26410
27244
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
26411
- logger.warn(error.message);
27245
+ this.warn(error.message);
26412
27246
  hls.trigger(Events.ERROR, {
26413
27247
  type: ErrorTypes.MEDIA_ERROR,
26414
27248
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -26476,7 +27310,7 @@ class GapController {
26476
27310
  }
26477
27311
  }
26478
27312
  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}`);
27313
+ this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
26480
27314
  this.moved = true;
26481
27315
  this.stalled = null;
26482
27316
  media.currentTime = targetTime;
@@ -26517,7 +27351,7 @@ class GapController {
26517
27351
  const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
26518
27352
  // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
26519
27353
  const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
26520
- logger.warn(error.message);
27354
+ this.warn(error.message);
26521
27355
  media.currentTime = targetTime;
26522
27356
  hls.trigger(Events.ERROR, {
26523
27357
  type: ErrorTypes.MEDIA_ERROR,
@@ -26527,7 +27361,7 @@ class GapController {
26527
27361
  });
26528
27362
  } else {
26529
27363
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
26530
- logger.error(error.message);
27364
+ this.error(error.message);
26531
27365
  hls.trigger(Events.ERROR, {
26532
27366
  type: ErrorTypes.MEDIA_ERROR,
26533
27367
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -26542,7 +27376,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
26542
27376
 
26543
27377
  class StreamController extends BaseStreamController {
26544
27378
  constructor(hls, fragmentTracker, keyLoader) {
26545
- super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
27379
+ super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
26546
27380
  this.audioCodecSwap = false;
26547
27381
  this.gapController = null;
26548
27382
  this.level = -1;
@@ -26550,27 +27384,43 @@ class StreamController extends BaseStreamController {
26550
27384
  this.altAudio = false;
26551
27385
  this.audioOnly = false;
26552
27386
  this.fragPlaying = null;
26553
- this.onvplaying = null;
26554
- this.onvseeked = null;
26555
27387
  this.fragLastKbps = 0;
26556
27388
  this.couldBacktrack = false;
26557
27389
  this.backtrackFragment = null;
26558
27390
  this.audioCodecSwitch = false;
26559
27391
  this.videoBuffer = null;
26560
- this._registerListeners();
27392
+ this.onMediaPlaying = () => {
27393
+ // tick to speed up FRAG_CHANGED triggering
27394
+ this.tick();
27395
+ };
27396
+ this.onMediaSeeked = () => {
27397
+ const media = this.media;
27398
+ const currentTime = media ? media.currentTime : null;
27399
+ if (isFiniteNumber(currentTime)) {
27400
+ this.log(`Media seeked to ${currentTime.toFixed(3)}`);
27401
+ }
27402
+
27403
+ // If seeked was issued before buffer was appended do not tick immediately
27404
+ const bufferInfo = this.getMainFwdBufferInfo();
27405
+ if (bufferInfo === null || bufferInfo.len === 0) {
27406
+ this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
27407
+ return;
27408
+ }
27409
+
27410
+ // tick to speed up FRAG_CHANGED triggering
27411
+ this.tick();
27412
+ };
27413
+ this.registerListeners();
26561
27414
  }
26562
- _registerListeners() {
27415
+ registerListeners() {
27416
+ super.registerListeners();
26563
27417
  const {
26564
27418
  hls
26565
27419
  } = 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
27420
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
26570
27421
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
26571
27422
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
26572
27423
  hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26573
- hls.on(Events.ERROR, this.onError, this);
26574
27424
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
26575
27425
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
26576
27426
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -26578,17 +27428,14 @@ class StreamController extends BaseStreamController {
26578
27428
  hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
26579
27429
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
26580
27430
  }
26581
- _unregisterListeners() {
27431
+ unregisterListeners() {
27432
+ super.unregisterListeners();
26582
27433
  const {
26583
27434
  hls
26584
27435
  } = 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
27436
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
26589
27437
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
26590
27438
  hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26591
- hls.off(Events.ERROR, this.onError, this);
26592
27439
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
26593
27440
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
26594
27441
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -26597,7 +27444,9 @@ class StreamController extends BaseStreamController {
26597
27444
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
26598
27445
  }
26599
27446
  onHandlerDestroying() {
26600
- this._unregisterListeners();
27447
+ // @ts-ignore
27448
+ this.onMediaPlaying = this.onMediaSeeked = null;
27449
+ this.unregisterListeners();
26601
27450
  super.onHandlerDestroying();
26602
27451
  }
26603
27452
  startLoad(startPosition) {
@@ -26717,7 +27566,7 @@ class StreamController extends BaseStreamController {
26717
27566
  if (this.altAudio && this.audioOnly) {
26718
27567
  return;
26719
27568
  }
26720
- if (!(levels != null && levels[level])) {
27569
+ if (!this.buffering || !(levels != null && levels[level])) {
26721
27570
  return;
26722
27571
  }
26723
27572
  const levelInfo = levels[level];
@@ -26925,20 +27774,17 @@ class StreamController extends BaseStreamController {
26925
27774
  onMediaAttached(event, data) {
26926
27775
  super.onMediaAttached(event, data);
26927
27776
  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);
27777
+ media.addEventListener('playing', this.onMediaPlaying);
27778
+ media.addEventListener('seeked', this.onMediaSeeked);
26932
27779
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
26933
27780
  }
26934
27781
  onMediaDetaching() {
26935
27782
  const {
26936
27783
  media
26937
27784
  } = 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;
27785
+ if (media) {
27786
+ media.removeEventListener('playing', this.onMediaPlaying);
27787
+ media.removeEventListener('seeked', this.onMediaSeeked);
26942
27788
  this.videoBuffer = null;
26943
27789
  }
26944
27790
  this.fragPlaying = null;
@@ -26948,27 +27794,6 @@ class StreamController extends BaseStreamController {
26948
27794
  }
26949
27795
  super.onMediaDetaching();
26950
27796
  }
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
27797
  onManifestLoading() {
26973
27798
  // reset buffer on manifest loading
26974
27799
  this.log('Trigger BUFFER_RESET');
@@ -27260,8 +28085,10 @@ class StreamController extends BaseStreamController {
27260
28085
  }
27261
28086
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
27262
28087
  // 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);
28088
+ const state = this.state;
28089
+ const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
28090
+ const levelDetails = this.getLevelDetails();
28091
+ gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
27265
28092
  }
27266
28093
  this.lastCurrentTime = media.currentTime;
27267
28094
  }
@@ -27699,7 +28526,7 @@ class Hls {
27699
28526
  * Get the video-dev/hls.js package version.
27700
28527
  */
27701
28528
  static get version() {
27702
- return "1.5.4";
28529
+ return "1.5.5-0.canary.9978";
27703
28530
  }
27704
28531
 
27705
28532
  /**
@@ -27762,9 +28589,12 @@ class Hls {
27762
28589
  * The configuration object provided on player instantiation.
27763
28590
  */
27764
28591
  this.userConfig = void 0;
28592
+ /**
28593
+ * The logger functions used by this player instance, configured on player instantiation.
28594
+ */
28595
+ this.logger = void 0;
27765
28596
  this.coreComponents = void 0;
27766
28597
  this.networkControllers = void 0;
27767
- this.started = false;
27768
28598
  this._emitter = new EventEmitter();
27769
28599
  this._autoLevelCapping = -1;
27770
28600
  this._maxHdcpLevel = null;
@@ -27781,11 +28611,11 @@ class Hls {
27781
28611
  this._media = null;
27782
28612
  this.url = null;
27783
28613
  this.triggeringException = void 0;
27784
- enableLogs(userConfig.debug || false, 'Hls instance');
27785
- const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
28614
+ const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
28615
+ const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
27786
28616
  this.userConfig = userConfig;
27787
28617
  if (config.progressive) {
27788
- enableStreamingMode(config);
28618
+ enableStreamingMode(config, logger);
27789
28619
  }
27790
28620
 
27791
28621
  // core controllers and network loaders
@@ -27884,7 +28714,7 @@ class Hls {
27884
28714
  try {
27885
28715
  return this.emit(event, event, eventObject);
27886
28716
  } catch (error) {
27887
- logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
28717
+ this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
27888
28718
  // Prevent recursion in error event handlers that throw #5497
27889
28719
  if (!this.triggeringException) {
27890
28720
  this.triggeringException = true;
@@ -27910,7 +28740,7 @@ class Hls {
27910
28740
  * Dispose of the instance
27911
28741
  */
27912
28742
  destroy() {
27913
- logger.log('destroy');
28743
+ this.logger.log('destroy');
27914
28744
  this.trigger(Events.DESTROYING, undefined);
27915
28745
  this.detachMedia();
27916
28746
  this.removeAllListeners();
@@ -27931,7 +28761,7 @@ class Hls {
27931
28761
  * Attaches Hls.js to a media element
27932
28762
  */
27933
28763
  attachMedia(media) {
27934
- logger.log('attachMedia');
28764
+ this.logger.log('attachMedia');
27935
28765
  this._media = media;
27936
28766
  this.trigger(Events.MEDIA_ATTACHING, {
27937
28767
  media: media
@@ -27942,7 +28772,7 @@ class Hls {
27942
28772
  * Detach Hls.js from the media
27943
28773
  */
27944
28774
  detachMedia() {
27945
- logger.log('detachMedia');
28775
+ this.logger.log('detachMedia');
27946
28776
  this.trigger(Events.MEDIA_DETACHING, undefined);
27947
28777
  this._media = null;
27948
28778
  }
@@ -27959,7 +28789,7 @@ class Hls {
27959
28789
  });
27960
28790
  this._autoLevelCapping = -1;
27961
28791
  this._maxHdcpLevel = null;
27962
- logger.log(`loadSource:${loadingSource}`);
28792
+ this.logger.log(`loadSource:${loadingSource}`);
27963
28793
  if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
27964
28794
  this.detachMedia();
27965
28795
  this.attachMedia(media);
@@ -27978,8 +28808,7 @@ class Hls {
27978
28808
  * Defaults to -1 (None: starts from earliest point)
27979
28809
  */
27980
28810
  startLoad(startPosition = -1) {
27981
- logger.log(`startLoad(${startPosition})`);
27982
- this.started = true;
28811
+ this.logger.log(`startLoad(${startPosition})`);
27983
28812
  this.networkControllers.forEach(controller => {
27984
28813
  controller.startLoad(startPosition);
27985
28814
  });
@@ -27989,34 +28818,31 @@ class Hls {
27989
28818
  * Stop loading of any stream data.
27990
28819
  */
27991
28820
  stopLoad() {
27992
- logger.log('stopLoad');
27993
- this.started = false;
28821
+ this.logger.log('stopLoad');
27994
28822
  this.networkControllers.forEach(controller => {
27995
28823
  controller.stopLoad();
27996
28824
  });
27997
28825
  }
27998
28826
 
27999
28827
  /**
28000
- * Resumes stream controller segment loading if previously started.
28828
+ * Resumes stream controller segment loading after `pauseBuffering` has been called.
28001
28829
  */
28002
28830
  resumeBuffering() {
28003
- if (this.started) {
28004
- this.networkControllers.forEach(controller => {
28005
- if ('fragmentLoader' in controller) {
28006
- controller.startLoad(-1);
28007
- }
28008
- });
28009
- }
28831
+ this.networkControllers.forEach(controller => {
28832
+ if (controller.resumeBuffering) {
28833
+ controller.resumeBuffering();
28834
+ }
28835
+ });
28010
28836
  }
28011
28837
 
28012
28838
  /**
28013
- * Stops stream controller segment loading without changing 'started' state like stopLoad().
28839
+ * Prevents stream controller from loading new segments until `resumeBuffering` is called.
28014
28840
  * This allows for media buffering to be paused without interupting playlist loading.
28015
28841
  */
28016
28842
  pauseBuffering() {
28017
28843
  this.networkControllers.forEach(controller => {
28018
- if ('fragmentLoader' in controller) {
28019
- controller.stopLoad();
28844
+ if (controller.pauseBuffering) {
28845
+ controller.pauseBuffering();
28020
28846
  }
28021
28847
  });
28022
28848
  }
@@ -28025,7 +28851,7 @@ class Hls {
28025
28851
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
28026
28852
  */
28027
28853
  swapAudioCodec() {
28028
- logger.log('swapAudioCodec');
28854
+ this.logger.log('swapAudioCodec');
28029
28855
  this.streamController.swapAudioCodec();
28030
28856
  }
28031
28857
 
@@ -28036,7 +28862,7 @@ class Hls {
28036
28862
  * Automatic recovery of media-errors by this process is configurable.
28037
28863
  */
28038
28864
  recoverMediaError() {
28039
- logger.log('recoverMediaError');
28865
+ this.logger.log('recoverMediaError');
28040
28866
  const media = this._media;
28041
28867
  this.detachMedia();
28042
28868
  if (media) {
@@ -28066,7 +28892,7 @@ class Hls {
28066
28892
  * 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
28893
  */
28068
28894
  set currentLevel(newLevel) {
28069
- logger.log(`set currentLevel:${newLevel}`);
28895
+ this.logger.log(`set currentLevel:${newLevel}`);
28070
28896
  this.levelController.manualLevel = newLevel;
28071
28897
  this.streamController.immediateLevelSwitch();
28072
28898
  }
@@ -28085,7 +28911,7 @@ class Hls {
28085
28911
  * @param newLevel - Pass -1 for automatic level selection
28086
28912
  */
28087
28913
  set nextLevel(newLevel) {
28088
- logger.log(`set nextLevel:${newLevel}`);
28914
+ this.logger.log(`set nextLevel:${newLevel}`);
28089
28915
  this.levelController.manualLevel = newLevel;
28090
28916
  this.streamController.nextLevelSwitch();
28091
28917
  }
@@ -28104,7 +28930,7 @@ class Hls {
28104
28930
  * @param newLevel - Pass -1 for automatic level selection
28105
28931
  */
28106
28932
  set loadLevel(newLevel) {
28107
- logger.log(`set loadLevel:${newLevel}`);
28933
+ this.logger.log(`set loadLevel:${newLevel}`);
28108
28934
  this.levelController.manualLevel = newLevel;
28109
28935
  }
28110
28936
 
@@ -28135,7 +28961,7 @@ class Hls {
28135
28961
  * Sets "first-level", see getter.
28136
28962
  */
28137
28963
  set firstLevel(newLevel) {
28138
- logger.log(`set firstLevel:${newLevel}`);
28964
+ this.logger.log(`set firstLevel:${newLevel}`);
28139
28965
  this.levelController.firstLevel = newLevel;
28140
28966
  }
28141
28967
 
@@ -28160,7 +28986,7 @@ class Hls {
28160
28986
  * (determined from download of first segment)
28161
28987
  */
28162
28988
  set startLevel(newLevel) {
28163
- logger.log(`set startLevel:${newLevel}`);
28989
+ this.logger.log(`set startLevel:${newLevel}`);
28164
28990
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
28165
28991
  if (newLevel !== -1) {
28166
28992
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -28235,7 +29061,7 @@ class Hls {
28235
29061
  */
28236
29062
  set autoLevelCapping(newLevel) {
28237
29063
  if (this._autoLevelCapping !== newLevel) {
28238
- logger.log(`set autoLevelCapping:${newLevel}`);
29064
+ this.logger.log(`set autoLevelCapping:${newLevel}`);
28239
29065
  this._autoLevelCapping = newLevel;
28240
29066
  this.levelController.checkMaxAutoUpdated();
28241
29067
  }