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

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 (69) 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 +2077 -1166
  5. package/dist/hls.js.d.ts +65 -50
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1149 -858
  8. package/dist/hls.light.js.map +1 -1
  9. package/dist/hls.light.min.js +1 -1
  10. package/dist/hls.light.min.js.map +1 -1
  11. package/dist/hls.light.mjs +985 -695
  12. package/dist/hls.light.mjs.map +1 -1
  13. package/dist/hls.min.js +1 -1
  14. package/dist/hls.min.js.map +1 -1
  15. package/dist/hls.mjs +1758 -862
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +21 -21
  20. package/src/config.ts +3 -2
  21. package/src/controller/abr-controller.ts +21 -20
  22. package/src/controller/audio-stream-controller.ts +15 -16
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +20 -8
  25. package/src/controller/base-stream-controller.ts +149 -33
  26. package/src/controller/buffer-controller.ts +11 -11
  27. package/src/controller/cap-level-controller.ts +1 -2
  28. package/src/controller/cmcd-controller.ts +27 -6
  29. package/src/controller/content-steering-controller.ts +8 -6
  30. package/src/controller/eme-controller.ts +9 -22
  31. package/src/controller/error-controller.ts +6 -8
  32. package/src/controller/fps-controller.ts +2 -3
  33. package/src/controller/gap-controller.ts +43 -16
  34. package/src/controller/latency-controller.ts +9 -11
  35. package/src/controller/level-controller.ts +12 -18
  36. package/src/controller/stream-controller.ts +24 -31
  37. package/src/controller/subtitle-stream-controller.ts +13 -14
  38. package/src/controller/subtitle-track-controller.ts +5 -3
  39. package/src/controller/timeline-controller.ts +23 -30
  40. package/src/crypt/aes-crypto.ts +21 -2
  41. package/src/crypt/decrypter-aes-mode.ts +4 -0
  42. package/src/crypt/decrypter.ts +32 -18
  43. package/src/crypt/fast-aes-key.ts +24 -5
  44. package/src/demux/audio/adts.ts +9 -4
  45. package/src/demux/sample-aes.ts +2 -0
  46. package/src/demux/transmuxer-interface.ts +4 -12
  47. package/src/demux/transmuxer-worker.ts +4 -4
  48. package/src/demux/transmuxer.ts +16 -3
  49. package/src/demux/tsdemuxer.ts +71 -37
  50. package/src/demux/video/avc-video-parser.ts +208 -119
  51. package/src/demux/video/base-video-parser.ts +134 -2
  52. package/src/demux/video/exp-golomb.ts +0 -208
  53. package/src/demux/video/hevc-video-parser.ts +746 -0
  54. package/src/events.ts +7 -0
  55. package/src/hls.ts +42 -34
  56. package/src/loader/fragment-loader.ts +9 -2
  57. package/src/loader/key-loader.ts +2 -0
  58. package/src/loader/level-key.ts +10 -9
  59. package/src/loader/playlist-loader.ts +4 -5
  60. package/src/remux/mp4-generator.ts +196 -1
  61. package/src/remux/mp4-remuxer.ts +23 -7
  62. package/src/task-loop.ts +5 -2
  63. package/src/types/component-api.ts +2 -0
  64. package/src/types/demuxer.ts +3 -0
  65. package/src/types/events.ts +4 -0
  66. package/src/utils/codecs.ts +33 -4
  67. package/src/utils/encryption-methods-util.ts +21 -0
  68. package/src/utils/logger.ts +54 -24
  69. package/src/utils/mp4-tools.ts +3 -1
package/dist/hls.mjs CHANGED
@@ -256,6 +256,7 @@ let Events = /*#__PURE__*/function (Events) {
256
256
  Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
257
257
  Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
258
258
  Events["MEDIA_DETACHED"] = "hlsMediaDetached";
259
+ Events["MEDIA_ENDED"] = "hlsMediaEnded";
259
260
  Events["BUFFER_RESET"] = "hlsBufferReset";
260
261
  Events["BUFFER_CODECS"] = "hlsBufferCodecs";
261
262
  Events["BUFFER_CREATED"] = "hlsBufferCreated";
@@ -369,58 +370,6 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
369
370
  return ErrorDetails;
370
371
  }({});
371
372
 
372
- const noop = function noop() {};
373
- const fakeLogger = {
374
- trace: noop,
375
- debug: noop,
376
- log: noop,
377
- warn: noop,
378
- info: noop,
379
- error: noop
380
- };
381
- let exportedLogger = fakeLogger;
382
-
383
- // let lastCallTime;
384
- // function formatMsgWithTimeInfo(type, msg) {
385
- // const now = Date.now();
386
- // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
387
- // lastCallTime = now;
388
- // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
389
- // return msg;
390
- // }
391
-
392
- function consolePrintFn(type) {
393
- const func = self.console[type];
394
- if (func) {
395
- return func.bind(self.console, `[${type}] >`);
396
- }
397
- return noop;
398
- }
399
- function exportLoggerFunctions(debugConfig, ...functions) {
400
- functions.forEach(function (type) {
401
- exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
402
- });
403
- }
404
- function enableLogs(debugConfig, id) {
405
- // check that console is available
406
- if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
407
- exportLoggerFunctions(debugConfig,
408
- // Remove out from list here to hard-disable a log-level
409
- // 'trace',
410
- 'debug', 'log', 'info', 'warn', 'error');
411
- // Some browsers don't allow to use bind on console object anyway
412
- // fallback to default if needed
413
- try {
414
- exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.6"}`);
415
- } catch (e) {
416
- exportedLogger = fakeLogger;
417
- }
418
- } else {
419
- exportedLogger = fakeLogger;
420
- }
421
- }
422
- const logger = exportedLogger;
423
-
424
373
  const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
425
374
  const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
426
375
 
@@ -502,6 +451,79 @@ class AttrList {
502
451
  }
503
452
  }
504
453
 
454
+ class Logger {
455
+ constructor(label, logger) {
456
+ this.trace = void 0;
457
+ this.debug = void 0;
458
+ this.log = void 0;
459
+ this.warn = void 0;
460
+ this.info = void 0;
461
+ this.error = void 0;
462
+ const lb = `[${label}]:`;
463
+ this.trace = noop;
464
+ this.debug = logger.debug.bind(null, lb);
465
+ this.log = logger.log.bind(null, lb);
466
+ this.warn = logger.warn.bind(null, lb);
467
+ this.info = logger.info.bind(null, lb);
468
+ this.error = logger.error.bind(null, lb);
469
+ }
470
+ }
471
+ const noop = function noop() {};
472
+ const fakeLogger = {
473
+ trace: noop,
474
+ debug: noop,
475
+ log: noop,
476
+ warn: noop,
477
+ info: noop,
478
+ error: noop
479
+ };
480
+ function createLogger() {
481
+ return _extends({}, fakeLogger);
482
+ }
483
+
484
+ // let lastCallTime;
485
+ // function formatMsgWithTimeInfo(type, msg) {
486
+ // const now = Date.now();
487
+ // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
488
+ // lastCallTime = now;
489
+ // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
490
+ // return msg;
491
+ // }
492
+
493
+ function consolePrintFn(type, id) {
494
+ const func = self.console[type];
495
+ return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
496
+ }
497
+ function getLoggerFn(key, debugConfig, id) {
498
+ return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
499
+ }
500
+ const exportedLogger = createLogger();
501
+ function enableLogs(debugConfig, context, id) {
502
+ // check that console is available
503
+ const newLogger = createLogger();
504
+ if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
505
+ const keys = [
506
+ // Remove out from list here to hard-disable a log-level
507
+ // 'trace',
508
+ 'debug', 'log', 'info', 'warn', 'error'];
509
+ keys.forEach(key => {
510
+ newLogger[key] = getLoggerFn(key, debugConfig, id);
511
+ });
512
+ // Some browsers don't allow to use bind on console object anyway
513
+ // fallback to default if needed
514
+ try {
515
+ newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.7-0.canary.10015"}`);
516
+ } catch (e) {
517
+ /* log fn threw an exception. All logger methods are no-ops. */
518
+ return createLogger();
519
+ }
520
+ }
521
+ // global exported logger uses the log methods from last call to `enableLogs`
522
+ _extends(exportedLogger, newLogger);
523
+ return newLogger;
524
+ }
525
+ const logger = exportedLogger;
526
+
505
527
  // Avoid exporting const enum so that these values can be inlined
506
528
 
507
529
  function isDateRangeCueAttribute(attrName) {
@@ -1036,6 +1058,26 @@ function strToUtf8array(str) {
1036
1058
  return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
1037
1059
  }
1038
1060
 
1061
+ var DecrypterAesMode = {
1062
+ cbc: 0,
1063
+ ctr: 1
1064
+ };
1065
+
1066
+ function isFullSegmentEncryption(method) {
1067
+ return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
1068
+ }
1069
+ function getAesModeFromFullSegmentMethod(method) {
1070
+ switch (method) {
1071
+ case 'AES-128':
1072
+ case 'AES-256':
1073
+ return DecrypterAesMode.cbc;
1074
+ case 'AES-256-CTR':
1075
+ return DecrypterAesMode.ctr;
1076
+ default:
1077
+ throw new Error(`invalid full segment method ${method}`);
1078
+ }
1079
+ }
1080
+
1039
1081
  /** returns `undefined` is `self` is missing, e.g. in node */
1040
1082
  const optionalSelf = typeof self !== 'undefined' ? self : undefined;
1041
1083
 
@@ -1899,7 +1941,9 @@ function parseStsd(stsd) {
1899
1941
  }
1900
1942
  function skipBERInteger(bytes, i) {
1901
1943
  const limit = i + 5;
1902
- while (bytes[i++] & 0x80 && i < limit) {}
1944
+ while (bytes[i++] & 0x80 && i < limit) {
1945
+ /* do nothing */
1946
+ }
1903
1947
  return i;
1904
1948
  }
1905
1949
  function toHex(x) {
@@ -2688,12 +2732,12 @@ class LevelKey {
2688
2732
  this.keyFormatVersions = formatversions;
2689
2733
  this.iv = iv;
2690
2734
  this.encrypted = method ? method !== 'NONE' : false;
2691
- this.isCommonEncryption = this.encrypted && method !== 'AES-128';
2735
+ this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
2692
2736
  }
2693
2737
  isSupported() {
2694
2738
  // If it's Segment encryption or No encryption, just select that key system
2695
2739
  if (this.method) {
2696
- if (this.method === 'AES-128' || this.method === 'NONE') {
2740
+ if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
2697
2741
  return true;
2698
2742
  }
2699
2743
  if (this.keyFormat === 'identity') {
@@ -2715,14 +2759,13 @@ class LevelKey {
2715
2759
  if (!this.encrypted || !this.uri) {
2716
2760
  return null;
2717
2761
  }
2718
- if (this.method === 'AES-128' && this.uri && !this.iv) {
2762
+ if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
2719
2763
  if (typeof sn !== 'number') {
2720
2764
  // We are fetching decryption data for a initialization segment
2721
- // If the segment was encrypted with AES-128
2765
+ // If the segment was encrypted with AES-128/256
2722
2766
  // It must have an IV defined. We cannot substitute the Segment Number in.
2723
- if (this.method === 'AES-128' && !this.iv) {
2724
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2725
- }
2767
+ logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2768
+
2726
2769
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
2727
2770
  sn = 0;
2728
2771
  }
@@ -3001,23 +3044,28 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
3001
3044
  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
3002
3045
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
3003
3046
  }
3004
-
3005
- // Idealy fLaC and Opus would be first (spec-compliant) but
3006
- // some browsers will report that fLaC is supported then fail.
3007
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3008
3047
  const codecsToCheck = {
3048
+ // Idealy fLaC and Opus would be first (spec-compliant) but
3049
+ // some browsers will report that fLaC is supported then fail.
3050
+ // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3009
3051
  flac: ['flac', 'fLaC', 'FLAC'],
3010
- opus: ['opus', 'Opus']
3052
+ opus: ['opus', 'Opus'],
3053
+ // Replace audio codec info if browser does not support mp4a.40.34,
3054
+ // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
3055
+ 'mp4a.40.34': ['mp3']
3011
3056
  }[lowerCaseCodec];
3012
3057
  for (let i = 0; i < codecsToCheck.length; i++) {
3058
+ var _getMediaSource;
3013
3059
  if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
3014
3060
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
3015
3061
  return codecsToCheck[i];
3062
+ } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
3063
+ return '';
3016
3064
  }
3017
3065
  }
3018
3066
  return lowerCaseCodec;
3019
3067
  }
3020
- const AUDIO_CODEC_REGEXP = /flac|opus/i;
3068
+ const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
3021
3069
  function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
3022
3070
  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
3023
3071
  }
@@ -3040,6 +3088,16 @@ function convertAVC1ToAVCOTI(codec) {
3040
3088
  }
3041
3089
  return codec;
3042
3090
  }
3091
+ function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
3092
+ const MediaSource = getMediaSource(preferManagedMediaSource) || {
3093
+ isTypeSupported: () => false
3094
+ };
3095
+ return {
3096
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
3097
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
3098
+ ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
3099
+ };
3100
+ }
3043
3101
 
3044
3102
  const MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g;
3045
3103
  const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -3889,10 +3947,10 @@ class PlaylistLoader {
3889
3947
  const loaderContext = loader.context;
3890
3948
  if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
3891
3949
  // same URL can't overlap
3892
- logger.trace('[playlist-loader]: playlist request ongoing');
3950
+ this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
3893
3951
  return;
3894
3952
  }
3895
- logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3953
+ this.hls.logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3896
3954
  loader.abort();
3897
3955
  }
3898
3956
 
@@ -4002,7 +4060,7 @@ class PlaylistLoader {
4002
4060
  // alt audio rendition in which quality levels (main)
4003
4061
  // contains both audio+video. but with mixed audio track not signaled
4004
4062
  if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
4005
- logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
4063
+ this.hls.logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
4006
4064
  audioTracks.unshift({
4007
4065
  type: 'main',
4008
4066
  name: 'main',
@@ -4101,7 +4159,7 @@ class PlaylistLoader {
4101
4159
  message += ` id: ${context.id} group-id: "${context.groupId}"`;
4102
4160
  }
4103
4161
  const error = new Error(message);
4104
- logger.warn(`[playlist-loader]: ${message}`);
4162
+ this.hls.logger.warn(`[playlist-loader]: ${message}`);
4105
4163
  let details = ErrorDetails.UNKNOWN;
4106
4164
  let fatal = false;
4107
4165
  const loader = this.getInternalLoader(context);
@@ -4706,7 +4764,47 @@ class LatencyController {
4706
4764
  this.currentTime = 0;
4707
4765
  this.stallCount = 0;
4708
4766
  this._latency = null;
4709
- this.timeupdateHandler = () => this.timeupdate();
4767
+ this.onTimeupdate = () => {
4768
+ const {
4769
+ media,
4770
+ levelDetails
4771
+ } = this;
4772
+ if (!media || !levelDetails) {
4773
+ return;
4774
+ }
4775
+ this.currentTime = media.currentTime;
4776
+ const latency = this.computeLatency();
4777
+ if (latency === null) {
4778
+ return;
4779
+ }
4780
+ this._latency = latency;
4781
+
4782
+ // Adapt playbackRate to meet target latency in low-latency mode
4783
+ const {
4784
+ lowLatencyMode,
4785
+ maxLiveSyncPlaybackRate
4786
+ } = this.config;
4787
+ if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4788
+ return;
4789
+ }
4790
+ const targetLatency = this.targetLatency;
4791
+ if (targetLatency === null) {
4792
+ return;
4793
+ }
4794
+ const distanceFromTarget = latency - targetLatency;
4795
+ // Only adjust playbackRate when within one target duration of targetLatency
4796
+ // and more than one second from under-buffering.
4797
+ // Playback further than one target duration from target can be considered DVR playback.
4798
+ const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4799
+ const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4800
+ if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4801
+ const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4802
+ const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4803
+ media.playbackRate = Math.min(max, Math.max(1, rate));
4804
+ } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4805
+ media.playbackRate = 1;
4806
+ }
4807
+ };
4710
4808
  this.hls = hls;
4711
4809
  this.config = hls.config;
4712
4810
  this.registerListeners();
@@ -4798,7 +4896,7 @@ class LatencyController {
4798
4896
  this.onMediaDetaching();
4799
4897
  this.levelDetails = null;
4800
4898
  // @ts-ignore
4801
- this.hls = this.timeupdateHandler = null;
4899
+ this.hls = null;
4802
4900
  }
4803
4901
  registerListeners() {
4804
4902
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -4816,11 +4914,11 @@ class LatencyController {
4816
4914
  }
4817
4915
  onMediaAttached(event, data) {
4818
4916
  this.media = data.media;
4819
- this.media.addEventListener('timeupdate', this.timeupdateHandler);
4917
+ this.media.addEventListener('timeupdate', this.onTimeupdate);
4820
4918
  }
4821
4919
  onMediaDetaching() {
4822
4920
  if (this.media) {
4823
- this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4921
+ this.media.removeEventListener('timeupdate', this.onTimeupdate);
4824
4922
  this.media = null;
4825
4923
  }
4826
4924
  }
@@ -4834,10 +4932,10 @@ class LatencyController {
4834
4932
  }) {
4835
4933
  this.levelDetails = details;
4836
4934
  if (details.advanced) {
4837
- this.timeupdate();
4935
+ this.onTimeupdate();
4838
4936
  }
4839
4937
  if (!details.live && this.media) {
4840
- this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4938
+ this.media.removeEventListener('timeupdate', this.onTimeupdate);
4841
4939
  }
4842
4940
  }
4843
4941
  onError(event, data) {
@@ -4847,48 +4945,7 @@ class LatencyController {
4847
4945
  }
4848
4946
  this.stallCount++;
4849
4947
  if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
4850
- logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
4851
- }
4852
- }
4853
- timeupdate() {
4854
- const {
4855
- media,
4856
- levelDetails
4857
- } = this;
4858
- if (!media || !levelDetails) {
4859
- return;
4860
- }
4861
- this.currentTime = media.currentTime;
4862
- const latency = this.computeLatency();
4863
- if (latency === null) {
4864
- return;
4865
- }
4866
- this._latency = latency;
4867
-
4868
- // Adapt playbackRate to meet target latency in low-latency mode
4869
- const {
4870
- lowLatencyMode,
4871
- maxLiveSyncPlaybackRate
4872
- } = this.config;
4873
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4874
- return;
4875
- }
4876
- const targetLatency = this.targetLatency;
4877
- if (targetLatency === null) {
4878
- return;
4879
- }
4880
- const distanceFromTarget = latency - targetLatency;
4881
- // Only adjust playbackRate when within one target duration of targetLatency
4882
- // and more than one second from under-buffering.
4883
- // Playback further than one target duration from target can be considered DVR playback.
4884
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4885
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4886
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4887
- const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4888
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4889
- media.playbackRate = Math.min(max, Math.max(1, rate));
4890
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4891
- media.playbackRate = 1;
4948
+ this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
4892
4949
  }
4893
4950
  }
4894
4951
  estimateLiveEdge() {
@@ -5660,18 +5717,13 @@ var ErrorActionFlags = {
5660
5717
  MoveAllAlternatesMatchingHDCP: 2,
5661
5718
  SwitchToSDR: 4
5662
5719
  }; // Reserved for future use
5663
- class ErrorController {
5720
+ class ErrorController extends Logger {
5664
5721
  constructor(hls) {
5722
+ super('error-controller', hls.logger);
5665
5723
  this.hls = void 0;
5666
5724
  this.playlistError = 0;
5667
5725
  this.penalizedRenditions = {};
5668
- this.log = void 0;
5669
- this.warn = void 0;
5670
- this.error = void 0;
5671
5726
  this.hls = hls;
5672
- this.log = logger.log.bind(logger, `[info]:`);
5673
- this.warn = logger.warn.bind(logger, `[warning]:`);
5674
- this.error = logger.error.bind(logger, `[error]:`);
5675
5727
  this.registerListeners();
5676
5728
  }
5677
5729
  registerListeners() {
@@ -6023,16 +6075,13 @@ class ErrorController {
6023
6075
  }
6024
6076
  }
6025
6077
 
6026
- class BasePlaylistController {
6078
+ class BasePlaylistController extends Logger {
6027
6079
  constructor(hls, logPrefix) {
6080
+ super(logPrefix, hls.logger);
6028
6081
  this.hls = void 0;
6029
6082
  this.timer = -1;
6030
6083
  this.requestScheduled = -1;
6031
6084
  this.canLoad = false;
6032
- this.log = void 0;
6033
- this.warn = void 0;
6034
- this.log = logger.log.bind(logger, `${logPrefix}:`);
6035
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
6036
6085
  this.hls = hls;
6037
6086
  }
6038
6087
  destroy() {
@@ -6065,7 +6114,7 @@ class BasePlaylistController {
6065
6114
  try {
6066
6115
  uri = new self.URL(attr.URI, previous.url).href;
6067
6116
  } catch (error) {
6068
- logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
6117
+ this.warn(`Could not construct new URL for Rendition Report: ${error}`);
6069
6118
  uri = attr.URI || '';
6070
6119
  }
6071
6120
  // Use exact match. Otherwise, the last partial match, if any, will be used
@@ -6152,7 +6201,12 @@ class BasePlaylistController {
6152
6201
  const cdnAge = lastAdvanced + details.ageHeader;
6153
6202
  let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
6154
6203
  if (currentGoal > 0) {
6155
- if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
6204
+ if (cdnAge > details.targetduration * 3) {
6205
+ // Omit segment and part directives when the last response was more than 3 target durations ago,
6206
+ this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
6207
+ msn = undefined;
6208
+ part = undefined;
6209
+ } else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
6156
6210
  // If we attempted to get the next or latest playlist update, but currentGoal increased,
6157
6211
  // then we either can't catchup, or the "age" header cannot be trusted.
6158
6212
  this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
@@ -6824,8 +6878,9 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
6824
6878
  return -1;
6825
6879
  }
6826
6880
 
6827
- class AbrController {
6881
+ class AbrController extends Logger {
6828
6882
  constructor(_hls) {
6883
+ super('abr', _hls.logger);
6829
6884
  this.hls = void 0;
6830
6885
  this.lastLevelLoadSec = 0;
6831
6886
  this.lastLoadedFragLevel = -1;
@@ -6939,7 +6994,7 @@ class AbrController {
6939
6994
  this.resetEstimator(nextLoadLevelBitrate);
6940
6995
  }
6941
6996
  this.clearTimer();
6942
- logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6997
+ this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6943
6998
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
6944
6999
  Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
6945
7000
  Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
@@ -6959,7 +7014,7 @@ class AbrController {
6959
7014
  }
6960
7015
  resetEstimator(abrEwmaDefaultEstimate) {
6961
7016
  if (abrEwmaDefaultEstimate) {
6962
- logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
7017
+ this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6963
7018
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
6964
7019
  }
6965
7020
  this.firstSelection = -1;
@@ -7191,7 +7246,7 @@ class AbrController {
7191
7246
  }
7192
7247
  const firstLevel = this.hls.firstLevel;
7193
7248
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
7194
- logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7249
+ this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7195
7250
  return clamped;
7196
7251
  }
7197
7252
  get forcedAutoLevel() {
@@ -7269,13 +7324,13 @@ class AbrController {
7269
7324
  // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
7270
7325
  const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
7271
7326
  maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
7272
- logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7327
+ this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7273
7328
  // don't use conservative factor on bitrate test
7274
7329
  bwFactor = bwUpFactor = 1;
7275
7330
  }
7276
7331
  }
7277
7332
  const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
7278
- logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7333
+ this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7279
7334
  if (bestLevel > -1) {
7280
7335
  return bestLevel;
7281
7336
  }
@@ -7349,7 +7404,7 @@ class AbrController {
7349
7404
  currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
7350
7405
  currentFrameRate = minFramerate;
7351
7406
  currentBw = Math.max(currentBw, minBitrate);
7352
- logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
7407
+ this.log(`picked start tier ${JSON.stringify(startTier)}`);
7353
7408
  } else {
7354
7409
  currentCodecSet = level == null ? void 0 : level.codecSet;
7355
7410
  currentVideoRange = level == null ? void 0 : level.videoRange;
@@ -7376,11 +7431,11 @@ class AbrController {
7376
7431
  const levels = this.hls.levels;
7377
7432
  const index = levels.indexOf(levelInfo);
7378
7433
  if (decodingInfo.error) {
7379
- logger.warn(`[abr] MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7434
+ this.warn(`MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7380
7435
  } else if (!decodingInfo.supported) {
7381
- logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7436
+ this.warn(`Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7382
7437
  if (index > -1 && levels.length > 1) {
7383
- logger.log(`[abr] Removing unsupported level ${index}`);
7438
+ this.log(`Removing unsupported level ${index}`);
7384
7439
  this.hls.removeLevel(index);
7385
7440
  }
7386
7441
  }
@@ -7427,9 +7482,9 @@ class AbrController {
7427
7482
  const forcedAutoLevel = this.forcedAutoLevel;
7428
7483
  if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
7429
7484
  if (levelsSkipped.length) {
7430
- logger.trace(`[abr] Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
7485
+ this.trace(`Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
7431
7486
  }
7432
- logger.info(`[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
7487
+ this.info(`switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
7433
7488
  }
7434
7489
  if (firstSelection) {
7435
7490
  this.firstSelection = i;
@@ -7483,8 +7538,9 @@ class AbrController {
7483
7538
  * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
7484
7539
  * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
7485
7540
  */
7486
- class TaskLoop {
7487
- constructor() {
7541
+ class TaskLoop extends Logger {
7542
+ constructor(label, logger) {
7543
+ super(label, logger);
7488
7544
  this._boundTick = void 0;
7489
7545
  this._tickTimer = null;
7490
7546
  this._tickInterval = null;
@@ -8575,8 +8631,8 @@ function createLoaderContext(frag, part = null) {
8575
8631
  var _frag$decryptdata;
8576
8632
  let byteRangeStart = start;
8577
8633
  let byteRangeEnd = end;
8578
- if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
8579
- // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
8634
+ if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
8635
+ // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
8580
8636
  // has the unencrypted size specified in the range.
8581
8637
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
8582
8638
  const fragmentLen = end - start;
@@ -8609,6 +8665,9 @@ function createGapLoadError(frag, part) {
8609
8665
  (part ? part : frag).stats.aborted = true;
8610
8666
  return new LoadError(errorData);
8611
8667
  }
8668
+ function isMethodFullSegmentAesCbc(method) {
8669
+ return method === 'AES-128' || method === 'AES-256';
8670
+ }
8612
8671
  class LoadError extends Error {
8613
8672
  constructor(data) {
8614
8673
  super(data.error.message);
@@ -8618,33 +8677,61 @@ class LoadError extends Error {
8618
8677
  }
8619
8678
 
8620
8679
  class AESCrypto {
8621
- constructor(subtle, iv) {
8680
+ constructor(subtle, iv, aesMode) {
8622
8681
  this.subtle = void 0;
8623
8682
  this.aesIV = void 0;
8683
+ this.aesMode = void 0;
8624
8684
  this.subtle = subtle;
8625
8685
  this.aesIV = iv;
8686
+ this.aesMode = aesMode;
8626
8687
  }
8627
8688
  decrypt(data, key) {
8628
- return this.subtle.decrypt({
8629
- name: 'AES-CBC',
8630
- iv: this.aesIV
8631
- }, key, data);
8689
+ switch (this.aesMode) {
8690
+ case DecrypterAesMode.cbc:
8691
+ return this.subtle.decrypt({
8692
+ name: 'AES-CBC',
8693
+ iv: this.aesIV
8694
+ }, key, data);
8695
+ case DecrypterAesMode.ctr:
8696
+ return this.subtle.decrypt({
8697
+ name: 'AES-CTR',
8698
+ counter: this.aesIV,
8699
+ length: 64
8700
+ },
8701
+ //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
8702
+ key, data);
8703
+ default:
8704
+ throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
8705
+ }
8632
8706
  }
8633
8707
  }
8634
8708
 
8635
8709
  class FastAESKey {
8636
- constructor(subtle, key) {
8710
+ constructor(subtle, key, aesMode) {
8637
8711
  this.subtle = void 0;
8638
8712
  this.key = void 0;
8713
+ this.aesMode = void 0;
8639
8714
  this.subtle = subtle;
8640
8715
  this.key = key;
8716
+ this.aesMode = aesMode;
8641
8717
  }
8642
8718
  expandKey() {
8719
+ const subtleAlgoName = getSubtleAlgoName(this.aesMode);
8643
8720
  return this.subtle.importKey('raw', this.key, {
8644
- name: 'AES-CBC'
8721
+ name: subtleAlgoName
8645
8722
  }, false, ['encrypt', 'decrypt']);
8646
8723
  }
8647
8724
  }
8725
+ function getSubtleAlgoName(aesMode) {
8726
+ switch (aesMode) {
8727
+ case DecrypterAesMode.cbc:
8728
+ return 'AES-CBC';
8729
+ case DecrypterAesMode.ctr:
8730
+ return 'AES-CTR';
8731
+ default:
8732
+ throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
8733
+ }
8734
+ }
8648
8735
 
8649
8736
  // PKCS7
8650
8737
  function removePadding(array) {
@@ -8894,7 +8981,8 @@ class Decrypter {
8894
8981
  this.currentIV = null;
8895
8982
  this.currentResult = null;
8896
8983
  this.useSoftware = void 0;
8897
- this.useSoftware = config.enableSoftwareAES;
8984
+ this.enableSoftwareAES = void 0;
8985
+ this.enableSoftwareAES = config.enableSoftwareAES;
8898
8986
  this.removePKCS7Padding = removePKCS7Padding;
8899
8987
  // built in decryptor expects PKCS7 padding
8900
8988
  if (removePKCS7Padding) {
@@ -8907,9 +8995,7 @@ class Decrypter {
8907
8995
  /* no-op */
8908
8996
  }
8909
8997
  }
8910
- if (this.subtle === null) {
8911
- this.useSoftware = true;
8912
- }
8998
+ this.useSoftware = this.subtle === null;
8913
8999
  }
8914
9000
  destroy() {
8915
9001
  this.subtle = null;
@@ -8947,10 +9033,10 @@ class Decrypter {
8947
9033
  this.softwareDecrypter = null;
8948
9034
  }
8949
9035
  }
8950
- decrypt(data, key, iv) {
9036
+ decrypt(data, key, iv, aesMode) {
8951
9037
  if (this.useSoftware) {
8952
9038
  return new Promise((resolve, reject) => {
8953
- this.softwareDecrypt(new Uint8Array(data), key, iv);
9039
+ this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
8954
9040
  const decryptResult = this.flush();
8955
9041
  if (decryptResult) {
8956
9042
  resolve(decryptResult.buffer);
@@ -8959,17 +9045,21 @@ class Decrypter {
8959
9045
  }
8960
9046
  });
8961
9047
  }
8962
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
9048
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
8963
9049
  }
8964
9050
 
8965
9051
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
8966
9052
  // data is handled in the flush() call
8967
- softwareDecrypt(data, key, iv) {
9053
+ softwareDecrypt(data, key, iv, aesMode) {
8968
9054
  const {
8969
9055
  currentIV,
8970
9056
  currentResult,
8971
9057
  remainderData
8972
9058
  } = this;
9059
+ if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
9060
+ logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
9061
+ return null;
9062
+ }
8973
9063
  this.logOnce('JS AES decrypt');
8974
9064
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
8975
9065
  // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
@@ -9002,11 +9092,11 @@ class Decrypter {
9002
9092
  }
9003
9093
  return result;
9004
9094
  }
9005
- webCryptoDecrypt(data, key, iv) {
9095
+ webCryptoDecrypt(data, key, iv, aesMode) {
9006
9096
  const subtle = this.subtle;
9007
9097
  if (this.key !== key || !this.fastAesKey) {
9008
9098
  this.key = key;
9009
- this.fastAesKey = new FastAESKey(subtle, key);
9099
+ this.fastAesKey = new FastAESKey(subtle, key, aesMode);
9010
9100
  }
9011
9101
  return this.fastAesKey.expandKey().then(aesKey => {
9012
9102
  // decrypt using web crypto
@@ -9014,22 +9104,25 @@ class Decrypter {
9014
9104
  return Promise.reject(new Error('web crypto not initialized'));
9015
9105
  }
9016
9106
  this.logOnce('WebCrypto AES decrypt');
9017
- const crypto = new AESCrypto(subtle, new Uint8Array(iv));
9107
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
9018
9108
  return crypto.decrypt(data.buffer, aesKey);
9019
9109
  }).catch(err => {
9020
9110
  logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
9021
- return this.onWebCryptoError(data, key, iv);
9111
+ return this.onWebCryptoError(data, key, iv, aesMode);
9022
9112
  });
9023
9113
  }
9024
- onWebCryptoError(data, key, iv) {
9025
- this.useSoftware = true;
9026
- this.logEnabled = true;
9027
- this.softwareDecrypt(data, key, iv);
9028
- const decryptResult = this.flush();
9029
- if (decryptResult) {
9030
- return decryptResult.buffer;
9114
+ onWebCryptoError(data, key, iv, aesMode) {
9115
+ const enableSoftwareAES = this.enableSoftwareAES;
9116
+ if (enableSoftwareAES) {
9117
+ this.useSoftware = true;
9118
+ this.logEnabled = true;
9119
+ this.softwareDecrypt(data, key, iv, aesMode);
9120
+ const decryptResult = this.flush();
9121
+ if (decryptResult) {
9122
+ return decryptResult.buffer;
9123
+ }
9031
9124
  }
9032
- throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
9125
+ throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
9033
9126
  }
9034
9127
  getValidChunk(data) {
9035
9128
  let currentChunk = data;
@@ -9080,7 +9173,7 @@ const State = {
9080
9173
  };
9081
9174
  class BaseStreamController extends TaskLoop {
9082
9175
  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
9083
- super();
9176
+ super(logPrefix, hls.logger);
9084
9177
  this.hls = void 0;
9085
9178
  this.fragPrevious = null;
9086
9179
  this.fragCurrent = null;
@@ -9105,22 +9198,98 @@ class BaseStreamController extends TaskLoop {
9105
9198
  this.startFragRequested = false;
9106
9199
  this.decrypter = void 0;
9107
9200
  this.initPTS = [];
9108
- this.onvseeking = null;
9109
- this.onvended = null;
9110
- this.logPrefix = '';
9111
- this.log = void 0;
9112
- this.warn = void 0;
9201
+ this.buffering = true;
9202
+ this.loadingParts = false;
9203
+ this.onMediaSeeking = () => {
9204
+ const {
9205
+ config,
9206
+ fragCurrent,
9207
+ media,
9208
+ mediaBuffer,
9209
+ state
9210
+ } = this;
9211
+ const currentTime = media ? media.currentTime : 0;
9212
+ const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9213
+ this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9214
+ if (this.state === State.ENDED) {
9215
+ this.resetLoadingState();
9216
+ } else if (fragCurrent) {
9217
+ // Seeking while frag load is in progress
9218
+ const tolerance = config.maxFragLookUpTolerance;
9219
+ const fragStartOffset = fragCurrent.start - tolerance;
9220
+ const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9221
+ // if seeking out of buffered range or into new one
9222
+ if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9223
+ const pastFragment = currentTime > fragEndOffset;
9224
+ // if the seek position is outside the current fragment range
9225
+ if (currentTime < fragStartOffset || pastFragment) {
9226
+ if (pastFragment && fragCurrent.loader) {
9227
+ this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9228
+ fragCurrent.abortRequests();
9229
+ this.resetLoadingState();
9230
+ }
9231
+ this.fragPrevious = null;
9232
+ }
9233
+ }
9234
+ }
9235
+ if (media) {
9236
+ // Remove gap fragments
9237
+ this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9238
+ this.lastCurrentTime = currentTime;
9239
+ if (!this.loadingParts) {
9240
+ const bufferEnd = Math.max(bufferInfo.end, currentTime);
9241
+ const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
9242
+ if (shouldLoadParts) {
9243
+ this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
9244
+ this.loadingParts = shouldLoadParts;
9245
+ }
9246
+ }
9247
+ }
9248
+
9249
+ // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9250
+ if (!this.loadedmetadata && !bufferInfo.len) {
9251
+ this.nextLoadPosition = this.startPosition = currentTime;
9252
+ }
9253
+
9254
+ // Async tick to speed up processing
9255
+ this.tickImmediate();
9256
+ };
9257
+ this.onMediaEnded = () => {
9258
+ // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9259
+ this.startPosition = this.lastCurrentTime = 0;
9260
+ if (this.playlistType === PlaylistLevelType.MAIN) {
9261
+ this.hls.trigger(Events.MEDIA_ENDED, {
9262
+ stalled: false
9263
+ });
9264
+ }
9265
+ };
9113
9266
  this.playlistType = playlistType;
9114
- this.logPrefix = logPrefix;
9115
- this.log = logger.log.bind(logger, `${logPrefix}:`);
9116
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
9117
9267
  this.hls = hls;
9118
9268
  this.fragmentLoader = new FragmentLoader(hls.config);
9119
9269
  this.keyLoader = keyLoader;
9120
9270
  this.fragmentTracker = fragmentTracker;
9121
9271
  this.config = hls.config;
9122
9272
  this.decrypter = new Decrypter(hls.config);
9273
+ }
9274
+ registerListeners() {
9275
+ const {
9276
+ hls
9277
+ } = this;
9278
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
9279
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
9280
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
9123
9281
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9282
+ hls.on(Events.ERROR, this.onError, this);
9283
+ }
9284
+ unregisterListeners() {
9285
+ const {
9286
+ hls
9287
+ } = this;
9288
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
9289
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
9290
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
9291
+ hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9292
+ hls.off(Events.ERROR, this.onError, this);
9124
9293
  }
9125
9294
  doTick() {
9126
9295
  this.onTickEnd();
@@ -9144,6 +9313,12 @@ class BaseStreamController extends TaskLoop {
9144
9313
  this.clearNextTick();
9145
9314
  this.state = State.STOPPED;
9146
9315
  }
9316
+ pauseBuffering() {
9317
+ this.buffering = false;
9318
+ }
9319
+ resumeBuffering() {
9320
+ this.buffering = true;
9321
+ }
9147
9322
  _streamEnded(bufferInfo, levelDetails) {
9148
9323
  // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
9149
9324
  // of nothing loading/loaded return false
@@ -9174,10 +9349,8 @@ class BaseStreamController extends TaskLoop {
9174
9349
  }
9175
9350
  onMediaAttached(event, data) {
9176
9351
  const media = this.media = this.mediaBuffer = data.media;
9177
- this.onvseeking = this.onMediaSeeking.bind(this);
9178
- this.onvended = this.onMediaEnded.bind(this);
9179
- media.addEventListener('seeking', this.onvseeking);
9180
- media.addEventListener('ended', this.onvended);
9352
+ media.addEventListener('seeking', this.onMediaSeeking);
9353
+ media.addEventListener('ended', this.onMediaEnded);
9181
9354
  const config = this.config;
9182
9355
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
9183
9356
  this.startLoad(config.startPosition);
@@ -9191,10 +9364,9 @@ class BaseStreamController extends TaskLoop {
9191
9364
  }
9192
9365
 
9193
9366
  // remove video listeners
9194
- if (media && this.onvseeking && this.onvended) {
9195
- media.removeEventListener('seeking', this.onvseeking);
9196
- media.removeEventListener('ended', this.onvended);
9197
- this.onvseeking = this.onvended = null;
9367
+ if (media) {
9368
+ media.removeEventListener('seeking', this.onMediaSeeking);
9369
+ media.removeEventListener('ended', this.onMediaEnded);
9198
9370
  }
9199
9371
  if (this.keyLoader) {
9200
9372
  this.keyLoader.detach();
@@ -9204,56 +9376,8 @@ class BaseStreamController extends TaskLoop {
9204
9376
  this.fragmentTracker.removeAllFragments();
9205
9377
  this.stopLoad();
9206
9378
  }
9207
- onMediaSeeking() {
9208
- const {
9209
- config,
9210
- fragCurrent,
9211
- media,
9212
- mediaBuffer,
9213
- state
9214
- } = this;
9215
- const currentTime = media ? media.currentTime : 0;
9216
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9217
- this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9218
- if (this.state === State.ENDED) {
9219
- this.resetLoadingState();
9220
- } else if (fragCurrent) {
9221
- // Seeking while frag load is in progress
9222
- const tolerance = config.maxFragLookUpTolerance;
9223
- const fragStartOffset = fragCurrent.start - tolerance;
9224
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9225
- // if seeking out of buffered range or into new one
9226
- if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9227
- const pastFragment = currentTime > fragEndOffset;
9228
- // if the seek position is outside the current fragment range
9229
- if (currentTime < fragStartOffset || pastFragment) {
9230
- if (pastFragment && fragCurrent.loader) {
9231
- this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9232
- fragCurrent.abortRequests();
9233
- this.resetLoadingState();
9234
- }
9235
- this.fragPrevious = null;
9236
- }
9237
- }
9238
- }
9239
- if (media) {
9240
- // Remove gap fragments
9241
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9242
- this.lastCurrentTime = currentTime;
9243
- }
9244
-
9245
- // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9246
- if (!this.loadedmetadata && !bufferInfo.len) {
9247
- this.nextLoadPosition = this.startPosition = currentTime;
9248
- }
9249
-
9250
- // Async tick to speed up processing
9251
- this.tickImmediate();
9252
- }
9253
- onMediaEnded() {
9254
- // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9255
- this.startPosition = this.lastCurrentTime = 0;
9256
- }
9379
+ onManifestLoading() {}
9380
+ onError(event, data) {}
9257
9381
  onManifestLoaded(event, data) {
9258
9382
  this.startTimeOffset = data.startTimeOffset;
9259
9383
  this.initPTS = [];
@@ -9263,7 +9387,7 @@ class BaseStreamController extends TaskLoop {
9263
9387
  this.stopLoad();
9264
9388
  super.onHandlerDestroying();
9265
9389
  // @ts-ignore
9266
- this.hls = null;
9390
+ this.hls = this.onMediaSeeking = this.onMediaEnded = null;
9267
9391
  }
9268
9392
  onHandlerDestroyed() {
9269
9393
  this.state = State.STOPPED;
@@ -9394,10 +9518,10 @@ class BaseStreamController extends TaskLoop {
9394
9518
  const decryptData = frag.decryptdata;
9395
9519
 
9396
9520
  // check to see if the payload needs to be decrypted
9397
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
9521
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
9398
9522
  const startTime = self.performance.now();
9399
9523
  // decrypt init segment data
9400
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
9524
+ return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
9401
9525
  hls.trigger(Events.ERROR, {
9402
9526
  type: ErrorTypes.MEDIA_ERROR,
9403
9527
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -9509,7 +9633,7 @@ class BaseStreamController extends TaskLoop {
9509
9633
  }
9510
9634
  let keyLoadingPromise = null;
9511
9635
  if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
9512
- this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
9636
+ this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
9513
9637
  this.state = State.KEY_LOADING;
9514
9638
  this.fragCurrent = frag;
9515
9639
  keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
@@ -9530,8 +9654,16 @@ class BaseStreamController extends TaskLoop {
9530
9654
  } else if (!frag.encrypted && details.encryptedFragments.length) {
9531
9655
  this.keyLoader.loadClear(frag, details.encryptedFragments);
9532
9656
  }
9657
+ const fragPrevious = this.fragPrevious;
9658
+ if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
9659
+ const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
9660
+ if (shouldLoadParts !== this.loadingParts) {
9661
+ this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
9662
+ this.loadingParts = shouldLoadParts;
9663
+ }
9664
+ }
9533
9665
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
9534
- if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
9666
+ if (this.loadingParts && frag.sn !== 'initSegment') {
9535
9667
  const partList = details.partList;
9536
9668
  if (partList && progressCallback) {
9537
9669
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -9540,7 +9672,7 @@ class BaseStreamController extends TaskLoop {
9540
9672
  const partIndex = this.getNextPart(partList, frag, targetBufferTime);
9541
9673
  if (partIndex > -1) {
9542
9674
  const part = partList[partIndex];
9543
- this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
9675
+ this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
9544
9676
  this.nextLoadPosition = part.start + part.duration;
9545
9677
  this.state = State.FRAG_LOADING;
9546
9678
  let _result;
@@ -9569,7 +9701,14 @@ class BaseStreamController extends TaskLoop {
9569
9701
  }
9570
9702
  }
9571
9703
  }
9572
- this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
9704
+ if (frag.sn !== 'initSegment' && this.loadingParts) {
9705
+ this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
9706
+ this.loadingParts = false;
9707
+ } else if (!frag.url) {
9708
+ // Selected fragment hint for part but not loading parts
9709
+ return Promise.resolve(null);
9710
+ }
9711
+ this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
9573
9712
  // Don't update nextLoadPosition for fragments which are not buffered
9574
9713
  if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
9575
9714
  this.nextLoadPosition = frag.start + frag.duration;
@@ -9667,8 +9806,36 @@ class BaseStreamController extends TaskLoop {
9667
9806
  if (part) {
9668
9807
  part.stats.parsing.end = now;
9669
9808
  }
9809
+ // See if part loading should be disabled/enabled based on buffer and playback position.
9810
+ if (frag.sn !== 'initSegment') {
9811
+ const levelDetails = this.getLevelDetails();
9812
+ const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
9813
+ const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
9814
+ if (shouldLoadParts !== this.loadingParts) {
9815
+ this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
9816
+ this.loadingParts = shouldLoadParts;
9817
+ }
9818
+ }
9670
9819
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
9671
9820
  }
9821
+ shouldLoadParts(details, bufferEnd) {
9822
+ if (this.config.lowLatencyMode) {
9823
+ if (!details) {
9824
+ return this.loadingParts;
9825
+ }
9826
+ if (details != null && details.partList) {
9827
+ var _details$fragmentHint;
9828
+ // Buffer must be ahead of first part + duration of parts after last segment
9829
+ // and playback must be at or past segment adjacent to part list
9830
+ const firstPart = details.partList[0];
9831
+ const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
9832
+ if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
9833
+ return true;
9834
+ }
9835
+ }
9836
+ }
9837
+ return false;
9838
+ }
9672
9839
  getCurrentContext(chunkMeta) {
9673
9840
  const {
9674
9841
  levels,
@@ -9817,7 +9984,8 @@ class BaseStreamController extends TaskLoop {
9817
9984
  config
9818
9985
  } = this;
9819
9986
  const start = fragments[0].start;
9820
- let frag;
9987
+ const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
9988
+ let frag = null;
9821
9989
  if (levelDetails.live) {
9822
9990
  const initialLiveManifestSize = config.initialLiveManifestSize;
9823
9991
  if (fragLen < initialLiveManifestSize) {
@@ -9829,6 +9997,10 @@ class BaseStreamController extends TaskLoop {
9829
9997
  // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
9830
9998
  // we get the fragment matching that start time
9831
9999
  if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
10000
+ if (canLoadParts && !this.loadingParts) {
10001
+ this.log(`LL-Part loading ON for initial live fragment`);
10002
+ this.loadingParts = true;
10003
+ }
9832
10004
  frag = this.getInitialLiveFragment(levelDetails, fragments);
9833
10005
  this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
9834
10006
  }
@@ -9839,7 +10011,7 @@ class BaseStreamController extends TaskLoop {
9839
10011
 
9840
10012
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
9841
10013
  if (!frag) {
9842
- const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
10014
+ const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
9843
10015
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
9844
10016
  }
9845
10017
  return this.mapToInitFragWhenRequired(frag);
@@ -9961,7 +10133,7 @@ class BaseStreamController extends TaskLoop {
9961
10133
  } = levelDetails;
9962
10134
  const tolerance = config.maxFragLookUpTolerance;
9963
10135
  const partList = levelDetails.partList;
9964
- const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
10136
+ const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
9965
10137
  if (loadingParts && fragmentHint && !this.bitrateTest) {
9966
10138
  // Include incomplete fragment with parts at end
9967
10139
  fragments = fragments.concat(fragmentHint);
@@ -10154,7 +10326,7 @@ class BaseStreamController extends TaskLoop {
10154
10326
  errorAction.resolved = true;
10155
10327
  }
10156
10328
  } else {
10157
- logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10329
+ this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10158
10330
  return;
10159
10331
  }
10160
10332
  } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
@@ -10563,6 +10735,7 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
10563
10735
  */
10564
10736
  function getAudioConfig(observer, data, offset, audioCodec) {
10565
10737
  let adtsObjectType;
10738
+ let originalAdtsObjectType;
10566
10739
  let adtsExtensionSamplingIndex;
10567
10740
  let adtsChannelConfig;
10568
10741
  let config;
@@ -10570,7 +10743,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10570
10743
  const manifestCodec = audioCodec;
10571
10744
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
10572
10745
  // byte 2
10573
- adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10746
+ adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10574
10747
  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
10575
10748
  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
10576
10749
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
@@ -10587,8 +10760,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10587
10760
  // byte 3
10588
10761
  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
10589
10762
  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
10590
- // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
10591
- if (/firefox/i.test(userAgent)) {
10763
+ // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
10764
+ if (/firefox|palemoon/i.test(userAgent)) {
10592
10765
  if (adtsSamplingIndex >= 6) {
10593
10766
  adtsObjectType = 5;
10594
10767
  config = new Array(4);
@@ -10682,6 +10855,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10682
10855
  samplerate: adtsSamplingRates[adtsSamplingIndex],
10683
10856
  channelCount: adtsChannelConfig,
10684
10857
  codec: 'mp4a.40.' + adtsObjectType,
10858
+ parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
10685
10859
  manifestCodec
10686
10860
  };
10687
10861
  }
@@ -10736,7 +10910,8 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
10736
10910
  track.channelCount = config.channelCount;
10737
10911
  track.codec = config.codec;
10738
10912
  track.manifestCodec = config.manifestCodec;
10739
- logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10913
+ track.parsedCodec = config.parsedCodec;
10914
+ logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10740
10915
  }
10741
10916
  }
10742
10917
  function getFrameDuration(samplerate) {
@@ -11327,11 +11502,115 @@ class BaseVideoParser {
11327
11502
  logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
11328
11503
  }
11329
11504
  }
11330
- }
11505
+ parseNALu(track, array) {
11506
+ const len = array.byteLength;
11507
+ let state = track.naluState || 0;
11508
+ const lastState = state;
11509
+ const units = [];
11510
+ let i = 0;
11511
+ let value;
11512
+ let overflow;
11513
+ let unitType;
11514
+ let lastUnitStart = -1;
11515
+ let lastUnitType = 0;
11516
+ // logger.log('PES:' + Hex.hexDump(array));
11331
11517
 
11332
- /**
11333
- * Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
11334
- */
11518
+ if (state === -1) {
11519
+ // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
11520
+ lastUnitStart = 0;
11521
+ // NALu type is value read from offset 0
11522
+ lastUnitType = this.getNALuType(array, 0);
11523
+ state = 0;
11524
+ i = 1;
11525
+ }
11526
+ while (i < len) {
11527
+ value = array[i++];
11528
+ // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
11529
+ if (!state) {
11530
+ state = value ? 0 : 1;
11531
+ continue;
11532
+ }
11533
+ if (state === 1) {
11534
+ state = value ? 0 : 2;
11535
+ continue;
11536
+ }
11537
+ // here we have state either equal to 2 or 3
11538
+ if (!value) {
11539
+ state = 3;
11540
+ } else if (value === 1) {
11541
+ overflow = i - state - 1;
11542
+ if (lastUnitStart >= 0) {
11543
+ const unit = {
11544
+ data: array.subarray(lastUnitStart, overflow),
11545
+ type: lastUnitType
11546
+ };
11547
+ // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
11548
+ units.push(unit);
11549
+ } else {
11550
+ // lastUnitStart is undefined => this is the first start code found in this PES packet
11551
+ // first check if start code delimiter is overlapping between 2 PES packets,
11552
+ // ie it started in last packet (lastState not zero)
11553
+ // and ended at the beginning of this PES packet (i <= 4 - lastState)
11554
+ const lastUnit = this.getLastNalUnit(track.samples);
11555
+ if (lastUnit) {
11556
+ if (lastState && i <= 4 - lastState) {
11557
+ // start delimiter overlapping between PES packets
11558
+ // strip start delimiter bytes from the end of last NAL unit
11559
+ // check if lastUnit had a state different from zero
11560
+ if (lastUnit.state) {
11561
+ // strip last bytes
11562
+ lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
11563
+ }
11564
+ }
11565
+ // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
11566
+
11567
+ if (overflow > 0) {
11568
+ // logger.log('first NALU found with overflow:' + overflow);
11569
+ lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
11570
+ lastUnit.state = 0;
11571
+ }
11572
+ }
11573
+ }
11574
+ // check if we can read unit type
11575
+ if (i < len) {
11576
+ unitType = this.getNALuType(array, i);
11577
+ // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
11578
+ lastUnitStart = i;
11579
+ lastUnitType = unitType;
11580
+ state = 0;
11581
+ } else {
11582
+ // not enough byte to read unit type. let's read it on next PES parsing
11583
+ state = -1;
11584
+ }
11585
+ } else {
11586
+ state = 0;
11587
+ }
11588
+ }
11589
+ if (lastUnitStart >= 0 && state >= 0) {
11590
+ const unit = {
11591
+ data: array.subarray(lastUnitStart, len),
11592
+ type: lastUnitType,
11593
+ state: state
11594
+ };
11595
+ units.push(unit);
11596
+ // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
11597
+ }
11598
+ // no NALu found
11599
+ if (units.length === 0) {
11600
+ // append pes.data to previous NAL unit
11601
+ const lastUnit = this.getLastNalUnit(track.samples);
11602
+ if (lastUnit) {
11603
+ lastUnit.data = appendUint8Array(lastUnit.data, array);
11604
+ }
11605
+ }
11606
+ track.naluState = state;
11607
+ return units;
11608
+ }
11609
+ }
11610
+
11611
+ /**
11612
+ * Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
11613
+ */
11335
11614
 
11336
11615
  class ExpGolomb {
11337
11616
  constructor(data) {
@@ -11469,21 +11748,171 @@ class ExpGolomb {
11469
11748
  readUInt() {
11470
11749
  return this.readBits(32);
11471
11750
  }
11751
+ }
11752
+
11753
+ class AvcVideoParser extends BaseVideoParser {
11754
+ parsePES(track, textTrack, pes, last, duration) {
11755
+ const units = this.parseNALu(track, pes.data);
11756
+ let VideoSample = this.VideoSample;
11757
+ let push;
11758
+ let spsfound = false;
11759
+ // free pes.data to save up some memory
11760
+ pes.data = null;
11761
+
11762
+ // if new NAL units found and last sample still there, let's push ...
11763
+ // this helps parsing streams with missing AUD (only do this if AUD never found)
11764
+ if (VideoSample && units.length && !track.audFound) {
11765
+ this.pushAccessUnit(VideoSample, track);
11766
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
11767
+ }
11768
+ units.forEach(unit => {
11769
+ var _VideoSample2;
11770
+ switch (unit.type) {
11771
+ // NDR
11772
+ case 1:
11773
+ {
11774
+ let iskey = false;
11775
+ push = true;
11776
+ const data = unit.data;
11777
+ // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
11778
+ if (spsfound && data.length > 4) {
11779
+ // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
11780
+ const sliceType = this.readSliceType(data);
11781
+ // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
11782
+ // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
11783
+ // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
11784
+ // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
11785
+ // if (sliceType === 2 || sliceType === 7) {
11786
+ if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
11787
+ iskey = true;
11788
+ }
11789
+ }
11790
+ if (iskey) {
11791
+ var _VideoSample;
11792
+ // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
11793
+ if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
11794
+ this.pushAccessUnit(VideoSample, track);
11795
+ VideoSample = this.VideoSample = null;
11796
+ }
11797
+ }
11798
+ if (!VideoSample) {
11799
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11800
+ }
11801
+ VideoSample.frame = true;
11802
+ VideoSample.key = iskey;
11803
+ break;
11804
+ // IDR
11805
+ }
11806
+ case 5:
11807
+ push = true;
11808
+ // handle PES not starting with AUD
11809
+ // if we have frame data already, that cannot belong to the same frame, so force a push
11810
+ if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
11811
+ this.pushAccessUnit(VideoSample, track);
11812
+ VideoSample = this.VideoSample = null;
11813
+ }
11814
+ if (!VideoSample) {
11815
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11816
+ }
11817
+ VideoSample.key = true;
11818
+ VideoSample.frame = true;
11819
+ break;
11820
+ // SEI
11821
+ case 6:
11822
+ {
11823
+ push = true;
11824
+ parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
11825
+ break;
11826
+ // SPS
11827
+ }
11828
+ case 7:
11829
+ {
11830
+ var _track$pixelRatio, _track$pixelRatio2;
11831
+ push = true;
11832
+ spsfound = true;
11833
+ const sps = unit.data;
11834
+ const config = this.readSPS(sps);
11835
+ 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]) {
11836
+ track.width = config.width;
11837
+ track.height = config.height;
11838
+ track.pixelRatio = config.pixelRatio;
11839
+ track.sps = [sps];
11840
+ track.duration = duration;
11841
+ const codecarray = sps.subarray(1, 4);
11842
+ let codecstring = 'avc1.';
11843
+ for (let i = 0; i < 3; i++) {
11844
+ let h = codecarray[i].toString(16);
11845
+ if (h.length < 2) {
11846
+ h = '0' + h;
11847
+ }
11848
+ codecstring += h;
11849
+ }
11850
+ track.codec = codecstring;
11851
+ }
11852
+ break;
11853
+ }
11854
+ // PPS
11855
+ case 8:
11856
+ push = true;
11857
+ track.pps = [unit.data];
11858
+ break;
11859
+ // AUD
11860
+ case 9:
11861
+ push = true;
11862
+ track.audFound = true;
11863
+ if (VideoSample) {
11864
+ this.pushAccessUnit(VideoSample, track);
11865
+ }
11866
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
11867
+ break;
11868
+ // Filler Data
11869
+ case 12:
11870
+ push = true;
11871
+ break;
11872
+ default:
11873
+ push = false;
11874
+ if (VideoSample) {
11875
+ VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
11876
+ }
11877
+ break;
11878
+ }
11879
+ if (VideoSample && push) {
11880
+ const units = VideoSample.units;
11881
+ units.push(unit);
11882
+ }
11883
+ });
11884
+ // if last PES packet, push samples
11885
+ if (last && VideoSample) {
11886
+ this.pushAccessUnit(VideoSample, track);
11887
+ this.VideoSample = null;
11888
+ }
11889
+ }
11890
+ getNALuType(data, offset) {
11891
+ return data[offset] & 0x1f;
11892
+ }
11893
+ readSliceType(data) {
11894
+ const eg = new ExpGolomb(data);
11895
+ // skip NALu type
11896
+ eg.readUByte();
11897
+ // discard first_mb_in_slice
11898
+ eg.readUEG();
11899
+ // return slice_type
11900
+ return eg.readUEG();
11901
+ }
11472
11902
 
11473
11903
  /**
11474
- * Advance the ExpGolomb decoder past a scaling list. The scaling
11475
- * list is optionally transmitted as part of a sequence parameter
11904
+ * The scaling list is optionally transmitted as part of a sequence parameter
11476
11905
  * set and is not relevant to transmuxing.
11477
11906
  * @param count the number of entries in this scaling list
11478
11907
  * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
11479
11908
  */
11480
- skipScalingList(count) {
11909
+ skipScalingList(count, reader) {
11481
11910
  let lastScale = 8;
11482
11911
  let nextScale = 8;
11483
11912
  let deltaScale;
11484
11913
  for (let j = 0; j < count; j++) {
11485
11914
  if (nextScale !== 0) {
11486
- deltaScale = this.readEG();
11915
+ deltaScale = reader.readEG();
11487
11916
  nextScale = (lastScale + deltaScale + 256) % 256;
11488
11917
  }
11489
11918
  lastScale = nextScale === 0 ? lastScale : nextScale;
@@ -11498,7 +11927,8 @@ class ExpGolomb {
11498
11927
  * sequence parameter set, including the dimensions of the
11499
11928
  * associated video frames.
11500
11929
  */
11501
- readSPS() {
11930
+ readSPS(sps) {
11931
+ const eg = new ExpGolomb(sps);
11502
11932
  let frameCropLeftOffset = 0;
11503
11933
  let frameCropRightOffset = 0;
11504
11934
  let frameCropTopOffset = 0;
@@ -11506,13 +11936,13 @@ class ExpGolomb {
11506
11936
  let numRefFramesInPicOrderCntCycle;
11507
11937
  let scalingListCount;
11508
11938
  let i;
11509
- const readUByte = this.readUByte.bind(this);
11510
- const readBits = this.readBits.bind(this);
11511
- const readUEG = this.readUEG.bind(this);
11512
- const readBoolean = this.readBoolean.bind(this);
11513
- const skipBits = this.skipBits.bind(this);
11514
- const skipEG = this.skipEG.bind(this);
11515
- const skipUEG = this.skipUEG.bind(this);
11939
+ const readUByte = eg.readUByte.bind(eg);
11940
+ const readBits = eg.readBits.bind(eg);
11941
+ const readUEG = eg.readUEG.bind(eg);
11942
+ const readBoolean = eg.readBoolean.bind(eg);
11943
+ const skipBits = eg.skipBits.bind(eg);
11944
+ const skipEG = eg.skipEG.bind(eg);
11945
+ const skipUEG = eg.skipUEG.bind(eg);
11516
11946
  const skipScalingList = this.skipScalingList.bind(this);
11517
11947
  readUByte();
11518
11948
  const profileIdc = readUByte(); // profile_idc
@@ -11537,9 +11967,9 @@ class ExpGolomb {
11537
11967
  if (readBoolean()) {
11538
11968
  // seq_scaling_list_present_flag[ i ]
11539
11969
  if (i < 6) {
11540
- skipScalingList(16);
11970
+ skipScalingList(16, eg);
11541
11971
  } else {
11542
- skipScalingList(64);
11972
+ skipScalingList(64, eg);
11543
11973
  }
11544
11974
  }
11545
11975
  }
@@ -11644,19 +12074,15 @@ class ExpGolomb {
11644
12074
  pixelRatio: pixelRatio
11645
12075
  };
11646
12076
  }
11647
- readSliceType() {
11648
- // skip NALu type
11649
- this.readUByte();
11650
- // discard first_mb_in_slice
11651
- this.readUEG();
11652
- // return slice_type
11653
- return this.readUEG();
11654
- }
11655
12077
  }
11656
12078
 
11657
- class AvcVideoParser extends BaseVideoParser {
11658
- parseAVCPES(track, textTrack, pes, last, duration) {
11659
- const units = this.parseAVCNALu(track, pes.data);
12079
+ class HevcVideoParser extends BaseVideoParser {
12080
+ constructor(...args) {
12081
+ super(...args);
12082
+ this.initVPS = null;
12083
+ }
12084
+ parsePES(track, textTrack, pes, last, duration) {
12085
+ const units = this.parseNALu(track, pes.data);
11660
12086
  let VideoSample = this.VideoSample;
11661
12087
  let push;
11662
12088
  let spsfound = false;
@@ -11672,42 +12098,49 @@ class AvcVideoParser extends BaseVideoParser {
11672
12098
  units.forEach(unit => {
11673
12099
  var _VideoSample2;
11674
12100
  switch (unit.type) {
11675
- // NDR
12101
+ // NON-IDR, NON RANDOM ACCESS SLICE
12102
+ case 0:
11676
12103
  case 1:
11677
- {
11678
- let iskey = false;
11679
- push = true;
11680
- const data = unit.data;
11681
- // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
11682
- if (spsfound && data.length > 4) {
11683
- // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
11684
- const sliceType = new ExpGolomb(data).readSliceType();
11685
- // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
11686
- // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
11687
- // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
11688
- // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
11689
- // if (sliceType === 2 || sliceType === 7) {
11690
- if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
11691
- iskey = true;
11692
- }
11693
- }
11694
- if (iskey) {
11695
- var _VideoSample;
11696
- // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
11697
- if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
11698
- this.pushAccessUnit(VideoSample, track);
11699
- VideoSample = this.VideoSample = null;
11700
- }
11701
- }
11702
- if (!VideoSample) {
11703
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12104
+ case 2:
12105
+ case 3:
12106
+ case 4:
12107
+ case 5:
12108
+ case 6:
12109
+ case 7:
12110
+ case 8:
12111
+ case 9:
12112
+ if (!VideoSample) {
12113
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12114
+ }
12115
+ VideoSample.frame = true;
12116
+ push = true;
12117
+ break;
12118
+
12119
+ // CRA, BLA (random access picture)
12120
+ case 16:
12121
+ case 17:
12122
+ case 18:
12123
+ case 21:
12124
+ push = true;
12125
+ if (spsfound) {
12126
+ var _VideoSample;
12127
+ // handle PES not starting with AUD
12128
+ // if we have frame data already, that cannot belong to the same frame, so force a push
12129
+ if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
12130
+ this.pushAccessUnit(VideoSample, track);
12131
+ VideoSample = this.VideoSample = null;
11704
12132
  }
11705
- VideoSample.frame = true;
11706
- VideoSample.key = iskey;
11707
- break;
11708
- // IDR
11709
12133
  }
11710
- case 5:
12134
+ if (!VideoSample) {
12135
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12136
+ }
12137
+ VideoSample.key = true;
12138
+ VideoSample.frame = true;
12139
+ break;
12140
+
12141
+ // IDR
12142
+ case 19:
12143
+ case 20:
11711
12144
  push = true;
11712
12145
  // handle PES not starting with AUD
11713
12146
  // if we have frame data already, that cannot belong to the same frame, so force a push
@@ -11721,180 +12154,518 @@ class AvcVideoParser extends BaseVideoParser {
11721
12154
  VideoSample.key = true;
11722
12155
  VideoSample.frame = true;
11723
12156
  break;
12157
+
11724
12158
  // SEI
11725
- case 6:
11726
- {
11727
- push = true;
11728
- parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
11729
- break;
11730
- // SPS
11731
- }
11732
- case 7:
11733
- {
11734
- var _track$pixelRatio, _track$pixelRatio2;
11735
- push = true;
11736
- spsfound = true;
11737
- const sps = unit.data;
11738
- const expGolombDecoder = new ExpGolomb(sps);
11739
- const config = expGolombDecoder.readSPS();
11740
- if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) {
11741
- track.width = config.width;
11742
- track.height = config.height;
11743
- track.pixelRatio = config.pixelRatio;
11744
- track.sps = [sps];
11745
- track.duration = duration;
11746
- const codecarray = sps.subarray(1, 4);
11747
- let codecstring = 'avc1.';
11748
- for (let i = 0; i < 3; i++) {
11749
- let h = codecarray[i].toString(16);
11750
- if (h.length < 2) {
11751
- h = '0' + h;
11752
- }
11753
- codecstring += h;
11754
- }
11755
- track.codec = codecstring;
11756
- }
11757
- break;
11758
- }
11759
- // PPS
11760
- case 8:
12159
+ case 39:
11761
12160
  push = true;
11762
- track.pps = [unit.data];
11763
- break;
11764
- // AUD
11765
- case 9:
11766
- push = true;
11767
- track.audFound = true;
11768
- if (VideoSample) {
11769
- this.pushAccessUnit(VideoSample, track);
11770
- }
11771
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12161
+ parseSEIMessageFromNALu(unit.data, 2,
12162
+ // NALu header size
12163
+ pes.pts, textTrack.samples);
11772
12164
  break;
11773
- // Filler Data
11774
- case 12:
12165
+
12166
+ // VPS
12167
+ case 32:
11775
12168
  push = true;
11776
- break;
11777
- default:
11778
- push = false;
11779
- if (VideoSample) {
11780
- VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
12169
+ if (!track.vps) {
12170
+ const config = this.readVPS(unit.data);
12171
+ track.params = _objectSpread2({}, config);
12172
+ this.initVPS = unit.data;
11781
12173
  }
12174
+ track.vps = [unit.data];
11782
12175
  break;
11783
- }
11784
- if (VideoSample && push) {
11785
- const units = VideoSample.units;
11786
- units.push(unit);
11787
- }
11788
- });
11789
- // if last PES packet, push samples
11790
- if (last && VideoSample) {
11791
- this.pushAccessUnit(VideoSample, track);
11792
- this.VideoSample = null;
11793
- }
11794
- }
11795
- parseAVCNALu(track, array) {
11796
- const len = array.byteLength;
11797
- let state = track.naluState || 0;
11798
- const lastState = state;
11799
- const units = [];
11800
- let i = 0;
11801
- let value;
11802
- let overflow;
11803
- let unitType;
11804
- let lastUnitStart = -1;
11805
- let lastUnitType = 0;
11806
- // logger.log('PES:' + Hex.hexDump(array));
11807
12176
 
11808
- if (state === -1) {
11809
- // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
11810
- lastUnitStart = 0;
11811
- // NALu type is value read from offset 0
11812
- lastUnitType = array[0] & 0x1f;
11813
- state = 0;
11814
- i = 1;
11815
- }
11816
- while (i < len) {
11817
- value = array[i++];
11818
- // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
11819
- if (!state) {
11820
- state = value ? 0 : 1;
11821
- continue;
11822
- }
11823
- if (state === 1) {
11824
- state = value ? 0 : 2;
11825
- continue;
11826
- }
11827
- // here we have state either equal to 2 or 3
11828
- if (!value) {
11829
- state = 3;
11830
- } else if (value === 1) {
11831
- overflow = i - state - 1;
11832
- if (lastUnitStart >= 0) {
11833
- const unit = {
11834
- data: array.subarray(lastUnitStart, overflow),
11835
- type: lastUnitType
11836
- };
11837
- // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
11838
- units.push(unit);
11839
- } else {
11840
- // lastUnitStart is undefined => this is the first start code found in this PES packet
11841
- // first check if start code delimiter is overlapping between 2 PES packets,
11842
- // ie it started in last packet (lastState not zero)
11843
- // and ended at the beginning of this PES packet (i <= 4 - lastState)
11844
- const lastUnit = this.getLastNalUnit(track.samples);
11845
- if (lastUnit) {
11846
- if (lastState && i <= 4 - lastState) {
11847
- // start delimiter overlapping between PES packets
11848
- // strip start delimiter bytes from the end of last NAL unit
11849
- // check if lastUnit had a state different from zero
11850
- if (lastUnit.state) {
11851
- // strip last bytes
11852
- lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
12177
+ // SPS
12178
+ case 33:
12179
+ push = true;
12180
+ spsfound = true;
12181
+ if (typeof track.params === 'object') {
12182
+ if (track.vps !== undefined && track.vps[0] !== this.initVPS && track.sps !== undefined && !this.matchSPS(track.sps[0], unit.data)) {
12183
+ this.initVPS = track.vps[0];
12184
+ track.sps = track.pps = undefined;
12185
+ }
12186
+ if (!track.sps) {
12187
+ const config = this.readSPS(unit.data);
12188
+ track.width = config.width;
12189
+ track.height = config.height;
12190
+ track.pixelRatio = config.pixelRatio;
12191
+ track.duration = duration;
12192
+ track.codec = config.codecString;
12193
+ track.sps = [];
12194
+ for (const prop in config.params) {
12195
+ track.params[prop] = config.params[prop];
11853
12196
  }
11854
12197
  }
11855
- // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
12198
+ if (track.vps !== undefined && track.vps[0] === this.initVPS) {
12199
+ track.sps.push(unit.data);
12200
+ }
12201
+ }
12202
+ if (!VideoSample) {
12203
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12204
+ }
12205
+ VideoSample.key = true;
12206
+ break;
11856
12207
 
11857
- if (overflow > 0) {
11858
- // logger.log('first NALU found with overflow:' + overflow);
11859
- lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
11860
- lastUnit.state = 0;
12208
+ // PPS
12209
+ case 34:
12210
+ push = true;
12211
+ if (typeof track.params === 'object') {
12212
+ if (!track.pps) {
12213
+ track.pps = [];
12214
+ const config = this.readPPS(unit.data);
12215
+ for (const prop in config) {
12216
+ track.params[prop] = config[prop];
12217
+ }
12218
+ }
12219
+ if (this.initVPS !== null || track.pps.length === 0) {
12220
+ track.pps.push(unit.data);
11861
12221
  }
11862
12222
  }
12223
+ break;
12224
+
12225
+ // ACCESS UNIT DELIMITER
12226
+ case 35:
12227
+ push = true;
12228
+ track.audFound = true;
12229
+ if (VideoSample) {
12230
+ this.pushAccessUnit(VideoSample, track);
12231
+ }
12232
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12233
+ break;
12234
+ default:
12235
+ push = false;
12236
+ if (VideoSample) {
12237
+ VideoSample.debug += 'unknown or irrelevant NAL ' + unit.type + ' ';
12238
+ }
12239
+ break;
12240
+ }
12241
+ if (VideoSample && push) {
12242
+ const units = VideoSample.units;
12243
+ units.push(unit);
12244
+ }
12245
+ });
12246
+ // if last PES packet, push samples
12247
+ if (last && VideoSample) {
12248
+ this.pushAccessUnit(VideoSample, track);
12249
+ this.VideoSample = null;
12250
+ }
12251
+ }
12252
+ getNALuType(data, offset) {
12253
+ return (data[offset] & 0x7e) >>> 1;
12254
+ }
12255
+ ebsp2rbsp(arr) {
12256
+ const dst = new Uint8Array(arr.byteLength);
12257
+ let dstIdx = 0;
12258
+ for (let i = 0; i < arr.byteLength; i++) {
12259
+ if (i >= 2) {
12260
+ // Unescape: Skip 0x03 after 00 00
12261
+ if (arr[i] === 0x03 && arr[i - 1] === 0x00 && arr[i - 2] === 0x00) {
12262
+ continue;
11863
12263
  }
11864
- // check if we can read unit type
11865
- if (i < len) {
11866
- unitType = array[i] & 0x1f;
11867
- // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
11868
- lastUnitStart = i;
11869
- lastUnitType = unitType;
11870
- state = 0;
11871
- } else {
11872
- // not enough byte to read unit type. let's read it on next PES parsing
11873
- state = -1;
11874
- }
11875
- } else {
11876
- state = 0;
11877
12264
  }
12265
+ dst[dstIdx] = arr[i];
12266
+ dstIdx++;
11878
12267
  }
11879
- if (lastUnitStart >= 0 && state >= 0) {
11880
- const unit = {
11881
- data: array.subarray(lastUnitStart, len),
11882
- type: lastUnitType,
11883
- state: state
11884
- };
11885
- units.push(unit);
11886
- // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
12268
+ return new Uint8Array(dst.buffer, 0, dstIdx);
12269
+ }
12270
+ readVPS(vps) {
12271
+ const eg = new ExpGolomb(vps);
12272
+ // remove header
12273
+ eg.readUByte();
12274
+ eg.readUByte();
12275
+ eg.readBits(4); // video_parameter_set_id
12276
+ eg.skipBits(2);
12277
+ eg.readBits(6); // max_layers_minus1
12278
+ const max_sub_layers_minus1 = eg.readBits(3);
12279
+ const temporal_id_nesting_flag = eg.readBoolean();
12280
+ // ...vui fps can be here, but empty fps value is not critical for metadata
12281
+
12282
+ return {
12283
+ numTemporalLayers: max_sub_layers_minus1 + 1,
12284
+ temporalIdNested: temporal_id_nesting_flag
12285
+ };
12286
+ }
12287
+ readSPS(sps) {
12288
+ const eg = new ExpGolomb(this.ebsp2rbsp(sps));
12289
+ eg.readUByte();
12290
+ eg.readUByte();
12291
+ eg.readBits(4); //video_parameter_set_id
12292
+ const max_sub_layers_minus1 = eg.readBits(3);
12293
+ eg.readBoolean(); // temporal_id_nesting_flag
12294
+
12295
+ // profile_tier_level
12296
+ const general_profile_space = eg.readBits(2);
12297
+ const general_tier_flag = eg.readBoolean();
12298
+ const general_profile_idc = eg.readBits(5);
12299
+ const general_profile_compatibility_flags_1 = eg.readUByte();
12300
+ const general_profile_compatibility_flags_2 = eg.readUByte();
12301
+ const general_profile_compatibility_flags_3 = eg.readUByte();
12302
+ const general_profile_compatibility_flags_4 = eg.readUByte();
12303
+ const general_constraint_indicator_flags_1 = eg.readUByte();
12304
+ const general_constraint_indicator_flags_2 = eg.readUByte();
12305
+ const general_constraint_indicator_flags_3 = eg.readUByte();
12306
+ const general_constraint_indicator_flags_4 = eg.readUByte();
12307
+ const general_constraint_indicator_flags_5 = eg.readUByte();
12308
+ const general_constraint_indicator_flags_6 = eg.readUByte();
12309
+ const general_level_idc = eg.readUByte();
12310
+ const sub_layer_profile_present_flags = [];
12311
+ const sub_layer_level_present_flags = [];
12312
+ for (let i = 0; i < max_sub_layers_minus1; i++) {
12313
+ sub_layer_profile_present_flags.push(eg.readBoolean());
12314
+ sub_layer_level_present_flags.push(eg.readBoolean());
12315
+ }
12316
+ if (max_sub_layers_minus1 > 0) {
12317
+ for (let i = max_sub_layers_minus1; i < 8; i++) {
12318
+ eg.readBits(2);
12319
+ }
12320
+ }
12321
+ for (let i = 0; i < max_sub_layers_minus1; i++) {
12322
+ if (sub_layer_profile_present_flags[i]) {
12323
+ eg.readUByte(); // sub_layer_profile_space, sub_layer_tier_flag, sub_layer_profile_idc
12324
+ eg.readUByte();
12325
+ eg.readUByte();
12326
+ eg.readUByte();
12327
+ eg.readUByte(); // sub_layer_profile_compatibility_flag
12328
+ eg.readUByte();
12329
+ eg.readUByte();
12330
+ eg.readUByte();
12331
+ eg.readUByte();
12332
+ eg.readUByte();
12333
+ eg.readUByte();
12334
+ }
12335
+ if (sub_layer_level_present_flags[i]) {
12336
+ eg.readUByte();
12337
+ }
12338
+ }
12339
+ eg.readUEG(); // seq_parameter_set_id
12340
+ const chroma_format_idc = eg.readUEG();
12341
+ if (chroma_format_idc == 3) {
12342
+ eg.skipBits(1); //separate_colour_plane_flag
12343
+ }
12344
+ const pic_width_in_luma_samples = eg.readUEG();
12345
+ const pic_height_in_luma_samples = eg.readUEG();
12346
+ const conformance_window_flag = eg.readBoolean();
12347
+ let pic_left_offset = 0,
12348
+ pic_right_offset = 0,
12349
+ pic_top_offset = 0,
12350
+ pic_bottom_offset = 0;
12351
+ if (conformance_window_flag) {
12352
+ pic_left_offset += eg.readUEG();
12353
+ pic_right_offset += eg.readUEG();
12354
+ pic_top_offset += eg.readUEG();
12355
+ pic_bottom_offset += eg.readUEG();
12356
+ }
12357
+ const bit_depth_luma_minus8 = eg.readUEG();
12358
+ const bit_depth_chroma_minus8 = eg.readUEG();
12359
+ const log2_max_pic_order_cnt_lsb_minus4 = eg.readUEG();
12360
+ const sub_layer_ordering_info_present_flag = eg.readBoolean();
12361
+ for (let i = sub_layer_ordering_info_present_flag ? 0 : max_sub_layers_minus1; i <= max_sub_layers_minus1; i++) {
12362
+ eg.skipUEG(); // max_dec_pic_buffering_minus1[i]
12363
+ eg.skipUEG(); // max_num_reorder_pics[i]
12364
+ eg.skipUEG(); // max_latency_increase_plus1[i]
12365
+ }
12366
+ eg.skipUEG(); // log2_min_luma_coding_block_size_minus3
12367
+ eg.skipUEG(); // log2_diff_max_min_luma_coding_block_size
12368
+ eg.skipUEG(); // log2_min_transform_block_size_minus2
12369
+ eg.skipUEG(); // log2_diff_max_min_transform_block_size
12370
+ eg.skipUEG(); // max_transform_hierarchy_depth_inter
12371
+ eg.skipUEG(); // max_transform_hierarchy_depth_intra
12372
+ const scaling_list_enabled_flag = eg.readBoolean();
12373
+ if (scaling_list_enabled_flag) {
12374
+ const sps_scaling_list_data_present_flag = eg.readBoolean();
12375
+ if (sps_scaling_list_data_present_flag) {
12376
+ for (let sizeId = 0; sizeId < 4; sizeId++) {
12377
+ for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
12378
+ const scaling_list_pred_mode_flag = eg.readBoolean();
12379
+ if (!scaling_list_pred_mode_flag) {
12380
+ eg.readUEG(); // scaling_list_pred_matrix_id_delta
12381
+ } else {
12382
+ const coefNum = Math.min(64, 1 << 4 + (sizeId << 1));
12383
+ if (sizeId > 1) {
12384
+ eg.readEG();
12385
+ }
12386
+ for (let i = 0; i < coefNum; i++) {
12387
+ eg.readEG();
12388
+ }
12389
+ }
12390
+ }
12391
+ }
12392
+ }
11887
12393
  }
11888
- // no NALu found
11889
- if (units.length === 0) {
11890
- // append pes.data to previous NAL unit
11891
- const lastUnit = this.getLastNalUnit(track.samples);
11892
- if (lastUnit) {
11893
- lastUnit.data = appendUint8Array(lastUnit.data, array);
12394
+ eg.readBoolean(); // amp_enabled_flag
12395
+ eg.readBoolean(); // sample_adaptive_offset_enabled_flag
12396
+ const pcm_enabled_flag = eg.readBoolean();
12397
+ if (pcm_enabled_flag) {
12398
+ eg.readUByte();
12399
+ eg.skipUEG();
12400
+ eg.skipUEG();
12401
+ eg.readBoolean();
12402
+ }
12403
+ const num_short_term_ref_pic_sets = eg.readUEG();
12404
+ let num_delta_pocs = 0;
12405
+ for (let i = 0; i < num_short_term_ref_pic_sets; i++) {
12406
+ let inter_ref_pic_set_prediction_flag = false;
12407
+ if (i !== 0) {
12408
+ inter_ref_pic_set_prediction_flag = eg.readBoolean();
12409
+ }
12410
+ if (inter_ref_pic_set_prediction_flag) {
12411
+ if (i === num_short_term_ref_pic_sets) {
12412
+ eg.readUEG();
12413
+ }
12414
+ eg.readBoolean();
12415
+ eg.readUEG();
12416
+ let next_num_delta_pocs = 0;
12417
+ for (let j = 0; j <= num_delta_pocs; j++) {
12418
+ const used_by_curr_pic_flag = eg.readBoolean();
12419
+ let use_delta_flag = false;
12420
+ if (!used_by_curr_pic_flag) {
12421
+ use_delta_flag = eg.readBoolean();
12422
+ }
12423
+ if (used_by_curr_pic_flag || use_delta_flag) {
12424
+ next_num_delta_pocs++;
12425
+ }
12426
+ }
12427
+ num_delta_pocs = next_num_delta_pocs;
12428
+ } else {
12429
+ const num_negative_pics = eg.readUEG();
12430
+ const num_positive_pics = eg.readUEG();
12431
+ num_delta_pocs = num_negative_pics + num_positive_pics;
12432
+ for (let j = 0; j < num_negative_pics; j++) {
12433
+ eg.readUEG();
12434
+ eg.readBoolean();
12435
+ }
12436
+ for (let j = 0; j < num_positive_pics; j++) {
12437
+ eg.readUEG();
12438
+ eg.readBoolean();
12439
+ }
12440
+ }
12441
+ }
12442
+ const long_term_ref_pics_present_flag = eg.readBoolean();
12443
+ if (long_term_ref_pics_present_flag) {
12444
+ const num_long_term_ref_pics_sps = eg.readUEG();
12445
+ for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
12446
+ for (let j = 0; j < log2_max_pic_order_cnt_lsb_minus4 + 4; j++) {
12447
+ eg.readBits(1);
12448
+ }
12449
+ eg.readBits(1);
12450
+ }
12451
+ }
12452
+ let min_spatial_segmentation_idc = 0;
12453
+ let sar_width = 1,
12454
+ sar_height = 1;
12455
+ let fps_fixed = true,
12456
+ fps_den = 1,
12457
+ fps_num = 0;
12458
+ eg.readBoolean(); // sps_temporal_mvp_enabled_flag
12459
+ eg.readBoolean(); // strong_intra_smoothing_enabled_flag
12460
+ let default_display_window_flag = false;
12461
+ const vui_parameters_present_flag = eg.readBoolean();
12462
+ if (vui_parameters_present_flag) {
12463
+ const aspect_ratio_info_present_flag = eg.readBoolean();
12464
+ if (aspect_ratio_info_present_flag) {
12465
+ const aspect_ratio_idc = eg.readUByte();
12466
+ const sar_width_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];
12467
+ const sar_height_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1];
12468
+ if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) {
12469
+ sar_width = sar_width_table[aspect_ratio_idc - 1];
12470
+ sar_height = sar_height_table[aspect_ratio_idc - 1];
12471
+ } else if (aspect_ratio_idc === 255) {
12472
+ sar_width = eg.readBits(16);
12473
+ sar_height = eg.readBits(16);
12474
+ }
12475
+ }
12476
+ const overscan_info_present_flag = eg.readBoolean();
12477
+ if (overscan_info_present_flag) {
12478
+ eg.readBoolean();
12479
+ }
12480
+ const video_signal_type_present_flag = eg.readBoolean();
12481
+ if (video_signal_type_present_flag) {
12482
+ eg.readBits(3);
12483
+ eg.readBoolean();
12484
+ const colour_description_present_flag = eg.readBoolean();
12485
+ if (colour_description_present_flag) {
12486
+ eg.readUByte();
12487
+ eg.readUByte();
12488
+ eg.readUByte();
12489
+ }
12490
+ }
12491
+ const chroma_loc_info_present_flag = eg.readBoolean();
12492
+ if (chroma_loc_info_present_flag) {
12493
+ eg.readUEG();
12494
+ eg.readUEG();
12495
+ }
12496
+ eg.readBoolean(); // neutral_chroma_indication_flag
12497
+ eg.readBoolean(); // field_seq_flag
12498
+ eg.readBoolean(); // frame_field_info_present_flag
12499
+ default_display_window_flag = eg.readBoolean();
12500
+ if (default_display_window_flag) {
12501
+ pic_left_offset += eg.readUEG();
12502
+ pic_right_offset += eg.readUEG();
12503
+ pic_top_offset += eg.readUEG();
12504
+ pic_bottom_offset += eg.readUEG();
12505
+ }
12506
+ const vui_timing_info_present_flag = eg.readBoolean();
12507
+ if (vui_timing_info_present_flag) {
12508
+ fps_den = eg.readBits(32);
12509
+ fps_num = eg.readBits(32);
12510
+ const vui_poc_proportional_to_timing_flag = eg.readBoolean();
12511
+ if (vui_poc_proportional_to_timing_flag) {
12512
+ eg.readUEG();
12513
+ }
12514
+ const vui_hrd_parameters_present_flag = eg.readBoolean();
12515
+ if (vui_hrd_parameters_present_flag) {
12516
+ //const commonInfPresentFlag = true;
12517
+ //if (commonInfPresentFlag) {
12518
+ const nal_hrd_parameters_present_flag = eg.readBoolean();
12519
+ const vcl_hrd_parameters_present_flag = eg.readBoolean();
12520
+ let sub_pic_hrd_params_present_flag = false;
12521
+ if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
12522
+ sub_pic_hrd_params_present_flag = eg.readBoolean();
12523
+ if (sub_pic_hrd_params_present_flag) {
12524
+ eg.readUByte();
12525
+ eg.readBits(5);
12526
+ eg.readBoolean();
12527
+ eg.readBits(5);
12528
+ }
12529
+ eg.readBits(4); // bit_rate_scale
12530
+ eg.readBits(4); // cpb_size_scale
12531
+ if (sub_pic_hrd_params_present_flag) {
12532
+ eg.readBits(4);
12533
+ }
12534
+ eg.readBits(5);
12535
+ eg.readBits(5);
12536
+ eg.readBits(5);
12537
+ }
12538
+ //}
12539
+ for (let i = 0; i <= max_sub_layers_minus1; i++) {
12540
+ fps_fixed = eg.readBoolean(); // fixed_pic_rate_general_flag
12541
+ const fixed_pic_rate_within_cvs_flag = fps_fixed || eg.readBoolean();
12542
+ let low_delay_hrd_flag = false;
12543
+ if (fixed_pic_rate_within_cvs_flag) {
12544
+ eg.readEG();
12545
+ } else {
12546
+ low_delay_hrd_flag = eg.readBoolean();
12547
+ }
12548
+ const cpb_cnt = low_delay_hrd_flag ? 1 : eg.readUEG() + 1;
12549
+ if (nal_hrd_parameters_present_flag) {
12550
+ for (let j = 0; j < cpb_cnt; j++) {
12551
+ eg.readUEG();
12552
+ eg.readUEG();
12553
+ if (sub_pic_hrd_params_present_flag) {
12554
+ eg.readUEG();
12555
+ eg.readUEG();
12556
+ }
12557
+ eg.skipBits(1);
12558
+ }
12559
+ }
12560
+ if (vcl_hrd_parameters_present_flag) {
12561
+ for (let j = 0; j < cpb_cnt; j++) {
12562
+ eg.readUEG();
12563
+ eg.readUEG();
12564
+ if (sub_pic_hrd_params_present_flag) {
12565
+ eg.readUEG();
12566
+ eg.readUEG();
12567
+ }
12568
+ eg.skipBits(1);
12569
+ }
12570
+ }
12571
+ }
12572
+ }
11894
12573
  }
12574
+ const bitstream_restriction_flag = eg.readBoolean();
12575
+ if (bitstream_restriction_flag) {
12576
+ eg.readBoolean(); // tiles_fixed_structure_flag
12577
+ eg.readBoolean(); // motion_vectors_over_pic_boundaries_flag
12578
+ eg.readBoolean(); // restricted_ref_pic_lists_flag
12579
+ min_spatial_segmentation_idc = eg.readUEG();
12580
+ }
12581
+ }
12582
+ let width = pic_width_in_luma_samples,
12583
+ height = pic_height_in_luma_samples;
12584
+ if (conformance_window_flag || default_display_window_flag) {
12585
+ let chroma_scale_w = 1,
12586
+ chroma_scale_h = 1;
12587
+ if (chroma_format_idc === 1) {
12588
+ // YUV 420
12589
+ chroma_scale_w = chroma_scale_h = 2;
12590
+ } else if (chroma_format_idc == 2) {
12591
+ // YUV 422
12592
+ chroma_scale_w = 2;
12593
+ }
12594
+ width = pic_width_in_luma_samples - chroma_scale_w * pic_right_offset - chroma_scale_w * pic_left_offset;
12595
+ height = pic_height_in_luma_samples - chroma_scale_h * pic_bottom_offset - chroma_scale_h * pic_top_offset;
12596
+ }
12597
+ const profile_space_string = general_profile_space ? ['A', 'B', 'C'][general_profile_space] : '';
12598
+ 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;
12599
+ let profile_compatibility_rev = 0;
12600
+ for (let i = 0; i < 32; i++) {
12601
+ profile_compatibility_rev = (profile_compatibility_rev | (profile_compatibility_buf >> i & 1) << 31 - i) >>> 0; // reverse bit position (and cast as UInt32)
12602
+ }
12603
+ let profile_compatibility_flags_string = profile_compatibility_rev.toString(16);
12604
+ if (general_profile_idc === 1 && profile_compatibility_flags_string === '2') {
12605
+ profile_compatibility_flags_string = '6';
12606
+ }
12607
+ const tier_flag_string = general_tier_flag ? 'H' : 'L';
12608
+ return {
12609
+ codecString: `hvc1.${profile_space_string}${general_profile_idc}.${profile_compatibility_flags_string}.${tier_flag_string}${general_level_idc}.B0`,
12610
+ params: {
12611
+ general_tier_flag,
12612
+ general_profile_idc,
12613
+ general_profile_space,
12614
+ general_profile_compatibility_flags: [general_profile_compatibility_flags_1, general_profile_compatibility_flags_2, general_profile_compatibility_flags_3, general_profile_compatibility_flags_4],
12615
+ 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],
12616
+ general_level_idc,
12617
+ bit_depth: bit_depth_luma_minus8 + 8,
12618
+ bit_depth_luma_minus8,
12619
+ bit_depth_chroma_minus8,
12620
+ min_spatial_segmentation_idc,
12621
+ chroma_format_idc: chroma_format_idc,
12622
+ frame_rate: {
12623
+ fixed: fps_fixed,
12624
+ fps: fps_num / fps_den
12625
+ }
12626
+ },
12627
+ width,
12628
+ height,
12629
+ pixelRatio: [sar_width, sar_height]
12630
+ };
12631
+ }
12632
+ readPPS(pps) {
12633
+ const eg = new ExpGolomb(this.ebsp2rbsp(pps));
12634
+ eg.readUByte();
12635
+ eg.readUByte();
12636
+ eg.skipUEG(); // pic_parameter_set_id
12637
+ eg.skipUEG(); // seq_parameter_set_id
12638
+ eg.skipBits(2); // dependent_slice_segments_enabled_flag, output_flag_present_flag
12639
+ eg.skipBits(3); // num_extra_slice_header_bits
12640
+ eg.skipBits(2); // sign_data_hiding_enabled_flag, cabac_init_present_flag
12641
+ eg.skipUEG();
12642
+ eg.skipUEG();
12643
+ eg.skipEG(); // init_qp_minus26
12644
+ eg.skipBits(2); // constrained_intra_pred_flag, transform_skip_enabled_flag
12645
+ const cu_qp_delta_enabled_flag = eg.readBoolean();
12646
+ if (cu_qp_delta_enabled_flag) {
12647
+ eg.skipUEG();
12648
+ }
12649
+ eg.skipEG(); // cb_qp_offset
12650
+ eg.skipEG(); // cr_qp_offset
12651
+ eg.skipBits(4); // pps_slice_chroma_qp_offsets_present_flag, weighted_pred_flag, weighted_bipred_flag, transquant_bypass_enabled_flag
12652
+ const tiles_enabled_flag = eg.readBoolean();
12653
+ const entropy_coding_sync_enabled_flag = eg.readBoolean();
12654
+ let parallelismType = 1; // slice-based parallel decoding
12655
+ if (entropy_coding_sync_enabled_flag && tiles_enabled_flag) {
12656
+ parallelismType = 0; // mixed-type parallel decoding
12657
+ } else if (entropy_coding_sync_enabled_flag) {
12658
+ parallelismType = 3; // wavefront-based parallel decoding
12659
+ } else if (tiles_enabled_flag) {
12660
+ parallelismType = 2; // tile-based parallel decoding
11895
12661
  }
11896
- track.naluState = state;
11897
- return units;
12662
+ return {
12663
+ parallelismType
12664
+ };
12665
+ }
12666
+ matchSPS(sps1, sps2) {
12667
+ // compare without headers and VPS related params
12668
+ return String.fromCharCode.apply(null, sps1).substr(3) === String.fromCharCode.apply(null, sps2).substr(3);
11898
12669
  }
11899
12670
  }
11900
12671
 
@@ -11912,7 +12683,7 @@ class SampleAesDecrypter {
11912
12683
  });
11913
12684
  }
11914
12685
  decryptBuffer(encryptedData) {
11915
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
12686
+ return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
11916
12687
  }
11917
12688
 
11918
12689
  // AAC - encrypt all full 16 bytes blocks starting from offset 16
@@ -12026,7 +12797,7 @@ class TSDemuxer {
12026
12797
  this.observer = observer;
12027
12798
  this.config = config;
12028
12799
  this.typeSupported = typeSupported;
12029
- this.videoParser = new AvcVideoParser();
12800
+ this.videoParser = null;
12030
12801
  }
12031
12802
  static probe(data) {
12032
12803
  const syncOffset = TSDemuxer.syncOffset(data);
@@ -12191,7 +12962,21 @@ class TSDemuxer {
12191
12962
  case videoPid:
12192
12963
  if (stt) {
12193
12964
  if (videoData && (pes = parsePES(videoData))) {
12194
- this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
12965
+ if (this.videoParser === null) {
12966
+ switch (videoTrack.segmentCodec) {
12967
+ case 'avc':
12968
+ this.videoParser = new AvcVideoParser();
12969
+ break;
12970
+ case 'hevc':
12971
+ {
12972
+ this.videoParser = new HevcVideoParser();
12973
+ }
12974
+ break;
12975
+ }
12976
+ }
12977
+ if (this.videoParser !== null) {
12978
+ this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
12979
+ }
12195
12980
  }
12196
12981
  videoData = {
12197
12982
  data: [],
@@ -12358,8 +13143,22 @@ class TSDemuxer {
12358
13143
  // try to parse last PES packets
12359
13144
  let pes;
12360
13145
  if (videoData && (pes = parsePES(videoData))) {
12361
- this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
12362
- videoTrack.pesData = null;
13146
+ if (this.videoParser === null) {
13147
+ switch (videoTrack.segmentCodec) {
13148
+ case 'avc':
13149
+ this.videoParser = new AvcVideoParser();
13150
+ break;
13151
+ case 'hevc':
13152
+ {
13153
+ this.videoParser = new HevcVideoParser();
13154
+ }
13155
+ break;
13156
+ }
13157
+ }
13158
+ if (this.videoParser !== null) {
13159
+ this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
13160
+ videoTrack.pesData = null;
13161
+ }
12363
13162
  } else {
12364
13163
  // either avcData null or PES truncated, keep it for next frag parsing
12365
13164
  videoTrack.pesData = videoData;
@@ -12692,7 +13491,14 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
12692
13491
  logger.warn('Unsupported EC-3 in M2TS found');
12693
13492
  break;
12694
13493
  case 0x24:
12695
- logger.warn('Unsupported HEVC in M2TS found');
13494
+ // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
13495
+ {
13496
+ if (result.videoPid === -1) {
13497
+ result.videoPid = pid;
13498
+ result.segmentVideoCodec = 'hevc';
13499
+ logger.log('HEVC in M2TS found');
13500
+ }
13501
+ }
12696
13502
  break;
12697
13503
  }
12698
13504
  // move to the next table entry
@@ -12915,6 +13721,8 @@ class MP4 {
12915
13721
  avc1: [],
12916
13722
  // codingname
12917
13723
  avcC: [],
13724
+ hvc1: [],
13725
+ hvcC: [],
12918
13726
  btrt: [],
12919
13727
  dinf: [],
12920
13728
  dref: [],
@@ -13339,8 +14147,10 @@ class MP4 {
13339
14147
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
13340
14148
  }
13341
14149
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
13342
- } else {
14150
+ } else if (track.segmentCodec === 'avc') {
13343
14151
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
14152
+ } else {
14153
+ return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
13344
14154
  }
13345
14155
  }
13346
14156
  static tkhd(track) {
@@ -13478,6 +14288,84 @@ class MP4 {
13478
14288
  const result = appendUint8Array(MP4.FTYP, movie);
13479
14289
  return result;
13480
14290
  }
14291
+ static hvc1(track) {
14292
+ const ps = track.params;
14293
+ const units = [track.vps, track.sps, track.pps];
14294
+ const NALuLengthSize = 4;
14295
+ 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]);
14296
+
14297
+ // compute hvcC size in bytes
14298
+ let length = config.length;
14299
+ for (let i = 0; i < units.length; i += 1) {
14300
+ length += 3;
14301
+ for (let j = 0; j < units[i].length; j += 1) {
14302
+ length += 2 + units[i][j].length;
14303
+ }
14304
+ }
14305
+ const hvcC = new Uint8Array(length);
14306
+ hvcC.set(config, 0);
14307
+ length = config.length;
14308
+ // append parameter set units: one vps, one or more sps and pps
14309
+ const iMax = units.length - 1;
14310
+ for (let i = 0; i < units.length; i += 1) {
14311
+ hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
14312
+ length += 3;
14313
+ for (let j = 0; j < units[i].length; j += 1) {
14314
+ hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
14315
+ length += 2;
14316
+ hvcC.set(units[i][j], length);
14317
+ length += units[i][j].length;
14318
+ }
14319
+ }
14320
+ const hvcc = MP4.box(MP4.types.hvcC, hvcC);
14321
+ const width = track.width;
14322
+ const height = track.height;
14323
+ const hSpacing = track.pixelRatio[0];
14324
+ const vSpacing = track.pixelRatio[1];
14325
+ return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
14326
+ // reserved
14327
+ 0x00, 0x00, 0x00,
14328
+ // reserved
14329
+ 0x00, 0x01,
14330
+ // data_reference_index
14331
+ 0x00, 0x00,
14332
+ // pre_defined
14333
+ 0x00, 0x00,
14334
+ // reserved
14335
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14336
+ // pre_defined
14337
+ width >> 8 & 0xff, width & 0xff,
14338
+ // width
14339
+ height >> 8 & 0xff, height & 0xff,
14340
+ // height
14341
+ 0x00, 0x48, 0x00, 0x00,
14342
+ // horizresolution
14343
+ 0x00, 0x48, 0x00, 0x00,
14344
+ // vertresolution
14345
+ 0x00, 0x00, 0x00, 0x00,
14346
+ // reserved
14347
+ 0x00, 0x01,
14348
+ // frame_count
14349
+ 0x12, 0x64, 0x61, 0x69, 0x6c,
14350
+ // dailymotion/hls.js
14351
+ 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,
14352
+ // compressorname
14353
+ 0x00, 0x18,
14354
+ // depth = 24
14355
+ 0x11, 0x11]),
14356
+ // pre_defined = -1
14357
+ hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
14358
+ // bufferSizeDB
14359
+ 0x00, 0x2d, 0xc6, 0xc0,
14360
+ // maxBitrate
14361
+ 0x00, 0x2d, 0xc6, 0xc0])),
14362
+ // avgBitrate
14363
+ MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
14364
+ // hSpacing
14365
+ hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
14366
+ // vSpacing
14367
+ vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
14368
+ }
13481
14369
  }
13482
14370
  MP4.types = void 0;
13483
14371
  MP4.HDLR_TYPES = void 0;
@@ -13859,9 +14747,9 @@ class MP4Remuxer {
13859
14747
  const foundOverlap = delta < -1;
13860
14748
  if (foundHole || foundOverlap) {
13861
14749
  if (foundHole) {
13862
- logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
14750
+ logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
13863
14751
  } else {
13864
- logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
14752
+ logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
13865
14753
  }
13866
14754
  if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
13867
14755
  firstDTS = nextAvcDts;
@@ -13870,12 +14758,24 @@ class MP4Remuxer {
13870
14758
  inputSamples[0].dts = firstDTS;
13871
14759
  inputSamples[0].pts = firstPTS;
13872
14760
  } else {
14761
+ let isPTSOrderRetained = true;
13873
14762
  for (let i = 0; i < inputSamples.length; i++) {
13874
- if (inputSamples[i].dts > firstPTS) {
14763
+ if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
13875
14764
  break;
13876
14765
  }
14766
+ const prevPTS = inputSamples[i].pts;
13877
14767
  inputSamples[i].dts -= delta;
13878
14768
  inputSamples[i].pts -= delta;
14769
+
14770
+ // check to see if this sample's PTS order has changed
14771
+ // relative to the next one
14772
+ if (i < inputSamples.length - 1) {
14773
+ const nextSamplePTS = inputSamples[i + 1].pts;
14774
+ const currentSamplePTS = inputSamples[i].pts;
14775
+ const currentOrder = nextSamplePTS <= currentSamplePTS;
14776
+ const prevOrder = nextSamplePTS <= prevPTS;
14777
+ isPTSOrderRetained = currentOrder == prevOrder;
14778
+ }
13879
14779
  }
13880
14780
  }
13881
14781
  logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
@@ -14023,7 +14923,7 @@ class MP4Remuxer {
14023
14923
  }
14024
14924
  }
14025
14925
  }
14026
- // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14926
+ // next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14027
14927
  mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
14028
14928
  this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
14029
14929
  this.videoSampleDuration = mp4SampleDuration;
@@ -14156,7 +15056,7 @@ class MP4Remuxer {
14156
15056
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
14157
15057
  for (let j = 0; j < missing; j++) {
14158
15058
  const newStamp = Math.max(nextPts, 0);
14159
- let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
15059
+ let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14160
15060
  if (!fillFrame) {
14161
15061
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
14162
15062
  fillFrame = sample.unit.subarray();
@@ -14284,7 +15184,7 @@ class MP4Remuxer {
14284
15184
  // samples count of this segment's duration
14285
15185
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
14286
15186
  // silent frame
14287
- const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
15187
+ const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14288
15188
  logger.warn('[mp4-remuxer]: remux empty Audio');
14289
15189
  // Can't remux if we can't generate a silent frame...
14290
15190
  if (!silentFrame) {
@@ -14678,13 +15578,15 @@ class Transmuxer {
14678
15578
  initSegmentData
14679
15579
  } = transmuxConfig;
14680
15580
  const keyData = getEncryptionType(uintData, decryptdata);
14681
- if (keyData && keyData.method === 'AES-128') {
15581
+ if (keyData && isFullSegmentEncryption(keyData.method)) {
14682
15582
  const decrypter = this.getDecrypter();
15583
+ const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
15584
+
14683
15585
  // Software decryption is synchronous; webCrypto is not
14684
15586
  if (decrypter.isSync()) {
14685
15587
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
14686
15588
  // data is handled in the flush() call
14687
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
15589
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
14688
15590
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
14689
15591
  const loadingParts = chunkMeta.part > -1;
14690
15592
  if (loadingParts) {
@@ -14696,7 +15598,7 @@ class Transmuxer {
14696
15598
  }
14697
15599
  uintData = new Uint8Array(decryptedData);
14698
15600
  } else {
14699
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
15601
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
14700
15602
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
14701
15603
  // the decrypted data has been transmuxed
14702
15604
  const result = this.push(decryptedData, null, chunkMeta);
@@ -15350,14 +16252,7 @@ class TransmuxerInterface {
15350
16252
  this.observer = new EventEmitter();
15351
16253
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
15352
16254
  this.observer.on(Events.ERROR, forwardMessage);
15353
- const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
15354
- isTypeSupported: () => false
15355
- };
15356
- const m2tsTypeSupported = {
15357
- mpeg: MediaSource.isTypeSupported('audio/mpeg'),
15358
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
15359
- ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
15360
- };
16255
+ const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
15361
16256
 
15362
16257
  // navigator.vendor is not always available in Web Worker
15363
16258
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -15645,7 +16540,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
15645
16540
 
15646
16541
  class AudioStreamController extends BaseStreamController {
15647
16542
  constructor(hls, fragmentTracker, keyLoader) {
15648
- super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);
16543
+ super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
15649
16544
  this.videoBuffer = null;
15650
16545
  this.videoTrackCC = -1;
15651
16546
  this.waitingVideoCC = -1;
@@ -15657,27 +16552,24 @@ class AudioStreamController extends BaseStreamController {
15657
16552
  this.flushing = false;
15658
16553
  this.bufferFlushed = false;
15659
16554
  this.cachedTrackLoadedData = null;
15660
- this._registerListeners();
16555
+ this.registerListeners();
15661
16556
  }
15662
16557
  onHandlerDestroying() {
15663
- this._unregisterListeners();
16558
+ this.unregisterListeners();
15664
16559
  super.onHandlerDestroying();
15665
16560
  this.mainDetails = null;
15666
16561
  this.bufferedTrack = null;
15667
16562
  this.switchingTrack = null;
15668
16563
  }
15669
- _registerListeners() {
16564
+ registerListeners() {
16565
+ super.registerListeners();
15670
16566
  const {
15671
16567
  hls
15672
16568
  } = this;
15673
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
15674
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
15675
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
15676
16569
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
15677
16570
  hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
15678
16571
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
15679
16572
  hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15680
- hls.on(Events.ERROR, this.onError, this);
15681
16573
  hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
15682
16574
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
15683
16575
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -15685,18 +16577,18 @@ class AudioStreamController extends BaseStreamController {
15685
16577
  hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
15686
16578
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
15687
16579
  }
15688
- _unregisterListeners() {
16580
+ unregisterListeners() {
15689
16581
  const {
15690
16582
  hls
15691
16583
  } = this;
15692
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
15693
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
15694
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16584
+ if (!hls) {
16585
+ return;
16586
+ }
16587
+ super.unregisterListeners();
15695
16588
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
15696
16589
  hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
15697
16590
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
15698
16591
  hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15699
- hls.off(Events.ERROR, this.onError, this);
15700
16592
  hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
15701
16593
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
15702
16594
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -15865,12 +16757,13 @@ class AudioStreamController extends BaseStreamController {
15865
16757
  } = this;
15866
16758
  const config = hls.config;
15867
16759
 
15868
- // 1. if video not attached AND
16760
+ // 1. if buffering is suspended
16761
+ // 2. if video not attached AND
15869
16762
  // start fragment already requested OR start frag prefetch not enabled
15870
- // 2. if tracks or track not loaded and selected
16763
+ // 3. if tracks or track not loaded and selected
15871
16764
  // then exit loop
15872
16765
  // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
15873
- if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
16766
+ if (!this.buffering || !media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
15874
16767
  return;
15875
16768
  }
15876
16769
  const levelInfo = levels[trackId];
@@ -16428,7 +17321,7 @@ class AudioStreamController extends BaseStreamController {
16428
17321
 
16429
17322
  class AudioTrackController extends BasePlaylistController {
16430
17323
  constructor(hls) {
16431
- super(hls, '[audio-track-controller]');
17324
+ super(hls, 'audio-track-controller');
16432
17325
  this.tracks = [];
16433
17326
  this.groupIds = null;
16434
17327
  this.tracksInGroup = [];
@@ -16747,26 +17640,23 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
16747
17640
 
16748
17641
  class SubtitleStreamController extends BaseStreamController {
16749
17642
  constructor(hls, fragmentTracker, keyLoader) {
16750
- super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);
17643
+ super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
16751
17644
  this.currentTrackId = -1;
16752
17645
  this.tracksBuffered = [];
16753
17646
  this.mainDetails = null;
16754
- this._registerListeners();
17647
+ this.registerListeners();
16755
17648
  }
16756
17649
  onHandlerDestroying() {
16757
- this._unregisterListeners();
17650
+ this.unregisterListeners();
16758
17651
  super.onHandlerDestroying();
16759
17652
  this.mainDetails = null;
16760
17653
  }
16761
- _registerListeners() {
17654
+ registerListeners() {
17655
+ super.registerListeners();
16762
17656
  const {
16763
17657
  hls
16764
17658
  } = this;
16765
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
16766
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
16767
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16768
17659
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16769
- hls.on(Events.ERROR, this.onError, this);
16770
17660
  hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
16771
17661
  hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
16772
17662
  hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -16774,15 +17664,12 @@ class SubtitleStreamController extends BaseStreamController {
16774
17664
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
16775
17665
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
16776
17666
  }
16777
- _unregisterListeners() {
17667
+ unregisterListeners() {
17668
+ super.unregisterListeners();
16778
17669
  const {
16779
17670
  hls
16780
17671
  } = this;
16781
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
16782
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
16783
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16784
17672
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16785
- hls.off(Events.ERROR, this.onError, this);
16786
17673
  hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
16787
17674
  hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
16788
17675
  hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -17009,10 +17896,10 @@ class SubtitleStreamController extends BaseStreamController {
17009
17896
  return;
17010
17897
  }
17011
17898
  // check to see if the payload needs to be decrypted
17012
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
17899
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
17013
17900
  const startTime = performance.now();
17014
17901
  // decrypt the subtitles
17015
- this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
17902
+ this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
17016
17903
  hls.trigger(Events.ERROR, {
17017
17904
  type: ErrorTypes.MEDIA_ERROR,
17018
17905
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -17146,7 +18033,7 @@ class BufferableInstance {
17146
18033
 
17147
18034
  class SubtitleTrackController extends BasePlaylistController {
17148
18035
  constructor(hls) {
17149
- super(hls, '[subtitle-track-controller]');
18036
+ super(hls, 'subtitle-track-controller');
17150
18037
  this.media = null;
17151
18038
  this.tracks = [];
17152
18039
  this.groupIds = null;
@@ -17155,10 +18042,10 @@ class SubtitleTrackController extends BasePlaylistController {
17155
18042
  this.currentTrack = null;
17156
18043
  this.selectDefaultTrack = true;
17157
18044
  this.queuedDefaultTrack = -1;
17158
- this.asyncPollTrackChange = () => this.pollTrackChange(0);
17159
18045
  this.useTextTrackPolling = false;
17160
18046
  this.subtitlePollingInterval = -1;
17161
18047
  this._subtitleDisplay = true;
18048
+ this.asyncPollTrackChange = () => this.pollTrackChange(0);
17162
18049
  this.onTextTracksChanged = () => {
17163
18050
  if (!this.useTextTrackPolling) {
17164
18051
  self.clearInterval(this.subtitlePollingInterval);
@@ -17192,6 +18079,7 @@ class SubtitleTrackController extends BasePlaylistController {
17192
18079
  this.tracks.length = 0;
17193
18080
  this.tracksInGroup.length = 0;
17194
18081
  this.currentTrack = null;
18082
+ // @ts-ignore
17195
18083
  this.onTextTracksChanged = this.asyncPollTrackChange = null;
17196
18084
  super.destroy();
17197
18085
  }
@@ -17652,8 +18540,9 @@ class BufferOperationQueue {
17652
18540
  }
17653
18541
 
17654
18542
  const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
17655
- class BufferController {
18543
+ class BufferController extends Logger {
17656
18544
  constructor(hls) {
18545
+ super('buffer-controller', hls.logger);
17657
18546
  // The level details used to determine duration, target-duration and live
17658
18547
  this.details = null;
17659
18548
  // cache the self generated object url to detect hijack of video tag
@@ -17683,9 +18572,6 @@ class BufferController {
17683
18572
  this.tracks = {};
17684
18573
  this.pendingTracks = {};
17685
18574
  this.sourceBuffer = void 0;
17686
- this.log = void 0;
17687
- this.warn = void 0;
17688
- this.error = void 0;
17689
18575
  this._onEndStreaming = event => {
17690
18576
  if (!this.hls) {
17691
18577
  return;
@@ -17731,15 +18617,11 @@ class BufferController {
17731
18617
  _objectUrl
17732
18618
  } = this;
17733
18619
  if (mediaSrc !== _objectUrl) {
17734
- logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
18620
+ this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
17735
18621
  }
17736
18622
  };
17737
18623
  this.hls = hls;
17738
- const logPrefix = '[buffer-controller]';
17739
18624
  this.appendSource = hls.config.preferManagedMediaSource;
17740
- this.log = logger.log.bind(logger, logPrefix);
17741
- this.warn = logger.warn.bind(logger, logPrefix);
17742
- this.error = logger.error.bind(logger, logPrefix);
17743
18625
  this._initSourceBuffer();
17744
18626
  this.registerListeners();
17745
18627
  }
@@ -17752,6 +18634,12 @@ class BufferController {
17752
18634
  this.lastMpegAudioChunk = null;
17753
18635
  // @ts-ignore
17754
18636
  this.hls = null;
18637
+ // @ts-ignore
18638
+ this._onMediaSourceOpen = this._onMediaSourceClose = null;
18639
+ // @ts-ignore
18640
+ this._onMediaSourceEnded = null;
18641
+ // @ts-ignore
18642
+ this._onStartStreaming = this._onEndStreaming = null;
17755
18643
  }
17756
18644
  registerListeners() {
17757
18645
  const {
@@ -17914,6 +18802,7 @@ class BufferController {
17914
18802
  this.resetBuffer(type);
17915
18803
  });
17916
18804
  this._initSourceBuffer();
18805
+ this.hls.resumeBuffering();
17917
18806
  }
17918
18807
  resetBuffer(type) {
17919
18808
  const sb = this.sourceBuffer[type];
@@ -21016,14 +21905,12 @@ class TimelineController {
21016
21905
  this.cea608Parser1 = this.cea608Parser2 = undefined;
21017
21906
  }
21018
21907
  initCea608Parsers() {
21019
- if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) {
21020
- const channel1 = new OutputFilter(this, 'textTrack1');
21021
- const channel2 = new OutputFilter(this, 'textTrack2');
21022
- const channel3 = new OutputFilter(this, 'textTrack3');
21023
- const channel4 = new OutputFilter(this, 'textTrack4');
21024
- this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
21025
- this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
21026
- }
21908
+ const channel1 = new OutputFilter(this, 'textTrack1');
21909
+ const channel2 = new OutputFilter(this, 'textTrack2');
21910
+ const channel3 = new OutputFilter(this, 'textTrack3');
21911
+ const channel4 = new OutputFilter(this, 'textTrack4');
21912
+ this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
21913
+ this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
21027
21914
  }
21028
21915
  addCues(trackName, startTime, endTime, screen, cueRanges) {
21029
21916
  // skip cues which overlap more than 50% with previously parsed time ranges
@@ -21261,7 +22148,7 @@ class TimelineController {
21261
22148
  if (inUseTracks != null && inUseTracks.length) {
21262
22149
  const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
21263
22150
  if (unusedTextTracks.length) {
21264
- logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
22151
+ this.hls.logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
21265
22152
  }
21266
22153
  }
21267
22154
  } else if (this.tracks.length) {
@@ -21306,26 +22193,23 @@ class TimelineController {
21306
22193
  return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
21307
22194
  }
21308
22195
  onFragLoading(event, data) {
21309
- this.initCea608Parsers();
21310
- const {
21311
- cea608Parser1,
21312
- cea608Parser2,
21313
- lastCc,
21314
- lastSn,
21315
- lastPartIndex
21316
- } = this;
21317
- if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
21318
- return;
21319
- }
21320
22196
  // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
21321
- if (data.frag.type === PlaylistLevelType.MAIN) {
22197
+ if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
21322
22198
  var _data$part$index, _data$part;
22199
+ const {
22200
+ cea608Parser1,
22201
+ cea608Parser2,
22202
+ lastSn
22203
+ } = this;
22204
+ if (!cea608Parser1 || !cea608Parser2) {
22205
+ return;
22206
+ }
21323
22207
  const {
21324
22208
  cc,
21325
22209
  sn
21326
22210
  } = data.frag;
21327
- const partIndex = (_data$part$index = data == null ? void 0 : (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
21328
- if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
22211
+ const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
22212
+ if (!(sn === lastSn + 1 || sn === lastSn && partIndex === this.lastPartIndex + 1 || cc === this.lastCc)) {
21329
22213
  cea608Parser1.reset();
21330
22214
  cea608Parser2.reset();
21331
22215
  }
@@ -21382,7 +22266,7 @@ class TimelineController {
21382
22266
  frag: frag
21383
22267
  });
21384
22268
  }, error => {
21385
- logger.log(`Failed to parse IMSC1: ${error}`);
22269
+ hls.logger.log(`Failed to parse IMSC1: ${error}`);
21386
22270
  hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
21387
22271
  success: false,
21388
22272
  frag: frag,
@@ -21423,7 +22307,7 @@ class TimelineController {
21423
22307
  this._fallbackToIMSC1(frag, payload);
21424
22308
  }
21425
22309
  // Something went wrong while parsing. Trigger event with success false.
21426
- logger.log(`Failed to parse VTT cue: ${error}`);
22310
+ hls.logger.log(`Failed to parse VTT cue: ${error}`);
21427
22311
  if (missingInitPTS && maxAvCC > frag.cc) {
21428
22312
  return;
21429
22313
  }
@@ -21484,12 +22368,7 @@ class TimelineController {
21484
22368
  this.captionsTracks = {};
21485
22369
  }
21486
22370
  onFragParsingUserdata(event, data) {
21487
- this.initCea608Parsers();
21488
- const {
21489
- cea608Parser1,
21490
- cea608Parser2
21491
- } = this;
21492
- if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
22371
+ if (!this.enabled || !this.config.enableCEA708Captions) {
21493
22372
  return;
21494
22373
  }
21495
22374
  const {
@@ -21504,9 +22383,12 @@ class TimelineController {
21504
22383
  for (let i = 0; i < samples.length; i++) {
21505
22384
  const ccBytes = samples[i].bytes;
21506
22385
  if (ccBytes) {
22386
+ if (!this.cea608Parser1) {
22387
+ this.initCea608Parsers();
22388
+ }
21507
22389
  const ccdatas = this.extractCea608Data(ccBytes);
21508
- cea608Parser1.addData(samples[i].pts, ccdatas[0]);
21509
- cea608Parser2.addData(samples[i].pts, ccdatas[1]);
22390
+ this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
22391
+ this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
21510
22392
  }
21511
22393
  }
21512
22394
  }
@@ -21702,7 +22584,7 @@ class CapLevelController {
21702
22584
  const hls = this.hls;
21703
22585
  const maxLevel = this.getMaxLevel(levels.length - 1);
21704
22586
  if (maxLevel !== this.autoLevelCapping) {
21705
- logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
22587
+ hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
21706
22588
  }
21707
22589
  hls.autoLevelCapping = maxLevel;
21708
22590
  if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
@@ -21880,10 +22762,10 @@ class FPSController {
21880
22762
  totalDroppedFrames: droppedFrames
21881
22763
  });
21882
22764
  if (droppedFPS > 0) {
21883
- // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
22765
+ // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
21884
22766
  if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
21885
22767
  let currentLevel = hls.currentLevel;
21886
- logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
22768
+ hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
21887
22769
  if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
21888
22770
  currentLevel = currentLevel - 1;
21889
22771
  hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
@@ -21915,7 +22797,6 @@ class FPSController {
21915
22797
  }
21916
22798
  }
21917
22799
 
21918
- const LOGGER_PREFIX = '[eme]';
21919
22800
  /**
21920
22801
  * Controller to deal with encrypted media extensions (EME)
21921
22802
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
@@ -21923,8 +22804,9 @@ const LOGGER_PREFIX = '[eme]';
21923
22804
  * @class
21924
22805
  * @constructor
21925
22806
  */
21926
- class EMEController {
22807
+ class EMEController extends Logger {
21927
22808
  constructor(hls) {
22809
+ super('eme', hls.logger);
21928
22810
  this.hls = void 0;
21929
22811
  this.config = void 0;
21930
22812
  this.media = null;
@@ -21934,12 +22816,100 @@ class EMEController {
21934
22816
  this.mediaKeySessions = [];
21935
22817
  this.keyIdToKeySessionPromise = {};
21936
22818
  this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
21937
- this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
21938
- this.onWaitingForKey = this._onWaitingForKey.bind(this);
21939
- this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
21940
- this.log = logger.log.bind(logger, LOGGER_PREFIX);
21941
- this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
21942
- this.error = logger.error.bind(logger, LOGGER_PREFIX);
22819
+ this.onMediaEncrypted = event => {
22820
+ const {
22821
+ initDataType,
22822
+ initData
22823
+ } = event;
22824
+ this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22825
+
22826
+ // Ignore event when initData is null
22827
+ if (initData === null) {
22828
+ return;
22829
+ }
22830
+ let keyId;
22831
+ let keySystemDomain;
22832
+ if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22833
+ // Match sinf keyId to playlist skd://keyId=
22834
+ const json = bin2str(new Uint8Array(initData));
22835
+ try {
22836
+ const sinf = base64Decode(JSON.parse(json).sinf);
22837
+ const tenc = parseSinf(new Uint8Array(sinf));
22838
+ if (!tenc) {
22839
+ return;
22840
+ }
22841
+ keyId = tenc.subarray(8, 24);
22842
+ keySystemDomain = KeySystems.FAIRPLAY;
22843
+ } catch (error) {
22844
+ this.warn('Failed to parse sinf "encrypted" event message initData');
22845
+ return;
22846
+ }
22847
+ } else {
22848
+ // Support clear-lead key-session creation (otherwise depend on playlist keys)
22849
+ const psshInfo = parsePssh(initData);
22850
+ if (psshInfo === null) {
22851
+ return;
22852
+ }
22853
+ if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22854
+ keyId = psshInfo.data.subarray(8, 24);
22855
+ }
22856
+ keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22857
+ }
22858
+ if (!keySystemDomain || !keyId) {
22859
+ return;
22860
+ }
22861
+ const keyIdHex = Hex.hexDump(keyId);
22862
+ const {
22863
+ keyIdToKeySessionPromise,
22864
+ mediaKeySessions
22865
+ } = this;
22866
+ let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22867
+ for (let i = 0; i < mediaKeySessions.length; i++) {
22868
+ // Match playlist key
22869
+ const keyContext = mediaKeySessions[i];
22870
+ const decryptdata = keyContext.decryptdata;
22871
+ if (decryptdata.pssh || !decryptdata.keyId) {
22872
+ continue;
22873
+ }
22874
+ const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22875
+ if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22876
+ keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22877
+ delete keyIdToKeySessionPromise[oldKeyIdHex];
22878
+ decryptdata.pssh = new Uint8Array(initData);
22879
+ decryptdata.keyId = keyId;
22880
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22881
+ return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22882
+ });
22883
+ break;
22884
+ }
22885
+ }
22886
+ if (!keySessionContextPromise) {
22887
+ // Clear-lead key (not encountered in playlist)
22888
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22889
+ keySystem,
22890
+ mediaKeys
22891
+ }) => {
22892
+ var _keySystemToKeySystem;
22893
+ this.throwIfDestroyed();
22894
+ const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22895
+ decryptdata.pssh = new Uint8Array(initData);
22896
+ decryptdata.keyId = keyId;
22897
+ return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22898
+ this.throwIfDestroyed();
22899
+ const keySessionContext = this.createMediaKeySessionContext({
22900
+ decryptdata,
22901
+ keySystem,
22902
+ mediaKeys
22903
+ });
22904
+ return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22905
+ });
22906
+ });
22907
+ }
22908
+ keySessionContextPromise.catch(error => this.handleError(error));
22909
+ };
22910
+ this.onWaitingForKey = event => {
22911
+ this.log(`"${event.type}" event`);
22912
+ };
21943
22913
  this.hls = hls;
21944
22914
  this.config = hls.config;
21945
22915
  this.registerListeners();
@@ -21953,9 +22923,9 @@ class EMEController {
21953
22923
  config.licenseXhrSetup = config.licenseResponseCallback = undefined;
21954
22924
  config.drmSystems = config.drmSystemOptions = {};
21955
22925
  // @ts-ignore
21956
- this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
22926
+ this.hls = this.config = this.keyIdToKeySessionPromise = null;
21957
22927
  // @ts-ignore
21958
- this.config = null;
22928
+ this.onMediaEncrypted = this.onWaitingForKey = null;
21959
22929
  }
21960
22930
  registerListeners() {
21961
22931
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -22219,100 +23189,6 @@ class EMEController {
22219
23189
  }
22220
23190
  return this.attemptKeySystemAccess(keySystemsToAttempt);
22221
23191
  }
22222
- _onMediaEncrypted(event) {
22223
- const {
22224
- initDataType,
22225
- initData
22226
- } = event;
22227
- this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22228
-
22229
- // Ignore event when initData is null
22230
- if (initData === null) {
22231
- return;
22232
- }
22233
- let keyId;
22234
- let keySystemDomain;
22235
- if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22236
- // Match sinf keyId to playlist skd://keyId=
22237
- const json = bin2str(new Uint8Array(initData));
22238
- try {
22239
- const sinf = base64Decode(JSON.parse(json).sinf);
22240
- const tenc = parseSinf(new Uint8Array(sinf));
22241
- if (!tenc) {
22242
- return;
22243
- }
22244
- keyId = tenc.subarray(8, 24);
22245
- keySystemDomain = KeySystems.FAIRPLAY;
22246
- } catch (error) {
22247
- this.warn('Failed to parse sinf "encrypted" event message initData');
22248
- return;
22249
- }
22250
- } else {
22251
- // Support clear-lead key-session creation (otherwise depend on playlist keys)
22252
- const psshInfo = parsePssh(initData);
22253
- if (psshInfo === null) {
22254
- return;
22255
- }
22256
- if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22257
- keyId = psshInfo.data.subarray(8, 24);
22258
- }
22259
- keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22260
- }
22261
- if (!keySystemDomain || !keyId) {
22262
- return;
22263
- }
22264
- const keyIdHex = Hex.hexDump(keyId);
22265
- const {
22266
- keyIdToKeySessionPromise,
22267
- mediaKeySessions
22268
- } = this;
22269
- let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22270
- for (let i = 0; i < mediaKeySessions.length; i++) {
22271
- // Match playlist key
22272
- const keyContext = mediaKeySessions[i];
22273
- const decryptdata = keyContext.decryptdata;
22274
- if (decryptdata.pssh || !decryptdata.keyId) {
22275
- continue;
22276
- }
22277
- const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22278
- if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22279
- keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22280
- delete keyIdToKeySessionPromise[oldKeyIdHex];
22281
- decryptdata.pssh = new Uint8Array(initData);
22282
- decryptdata.keyId = keyId;
22283
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22284
- return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22285
- });
22286
- break;
22287
- }
22288
- }
22289
- if (!keySessionContextPromise) {
22290
- // Clear-lead key (not encountered in playlist)
22291
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22292
- keySystem,
22293
- mediaKeys
22294
- }) => {
22295
- var _keySystemToKeySystem;
22296
- this.throwIfDestroyed();
22297
- const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22298
- decryptdata.pssh = new Uint8Array(initData);
22299
- decryptdata.keyId = keyId;
22300
- return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22301
- this.throwIfDestroyed();
22302
- const keySessionContext = this.createMediaKeySessionContext({
22303
- decryptdata,
22304
- keySystem,
22305
- mediaKeys
22306
- });
22307
- return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22308
- });
22309
- });
22310
- }
22311
- keySessionContextPromise.catch(error => this.handleError(error));
22312
- }
22313
- _onWaitingForKey(event) {
22314
- this.log(`"${event.type}" event`);
22315
- }
22316
23192
  attemptSetMediaKeys(keySystem, mediaKeys) {
22317
23193
  const queue = this.setMediaKeysQueue.slice();
22318
23194
  this.log(`Setting media-keys for "${keySystem}"`);
@@ -22905,20 +23781,6 @@ class SfItem {
22905
23781
  }
22906
23782
  }
22907
23783
 
22908
- /**
22909
- * A class to represent structured field tokens when `Symbol` is not available.
22910
- *
22911
- * @group Structured Field
22912
- *
22913
- * @beta
22914
- */
22915
- class SfToken {
22916
- constructor(description) {
22917
- this.description = void 0;
22918
- this.description = description;
22919
- }
22920
- }
22921
-
22922
23784
  const DICT = 'Dict';
22923
23785
 
22924
23786
  function format(value) {
@@ -22942,29 +23804,27 @@ function throwError(action, src, type, cause) {
22942
23804
  });
22943
23805
  }
22944
23806
 
22945
- const BARE_ITEM = 'Bare Item';
22946
-
22947
- const BOOLEAN = 'Boolean';
22948
-
22949
- const BYTES = 'Byte Sequence';
22950
-
22951
- const DECIMAL = 'Decimal';
22952
-
22953
- const INTEGER = 'Integer';
22954
-
22955
- function isInvalidInt(value) {
22956
- return value < -999999999999999 || 999999999999999 < value;
23807
+ function serializeError(src, type, cause) {
23808
+ return throwError('serialize', src, type, cause);
22957
23809
  }
22958
23810
 
22959
- const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
22960
-
22961
- const TOKEN = 'Token';
23811
+ /**
23812
+ * A class to represent structured field tokens when `Symbol` is not available.
23813
+ *
23814
+ * @group Structured Field
23815
+ *
23816
+ * @beta
23817
+ */
23818
+ class SfToken {
23819
+ constructor(description) {
23820
+ this.description = void 0;
23821
+ this.description = description;
23822
+ }
23823
+ }
22962
23824
 
22963
- const KEY = 'Key';
23825
+ const BARE_ITEM = 'Bare Item';
22964
23826
 
22965
- function serializeError(src, type, cause) {
22966
- return throwError('serialize', src, type, cause);
22967
- }
23827
+ const BOOLEAN = 'Boolean';
22968
23828
 
22969
23829
  // 4.1.9. Serializing a Boolean
22970
23830
  //
@@ -23003,6 +23863,8 @@ function base64encode(binary) {
23003
23863
  return btoa(String.fromCharCode(...binary));
23004
23864
  }
23005
23865
 
23866
+ const BYTES = 'Byte Sequence';
23867
+
23006
23868
  // 4.1.8. Serializing a Byte Sequence
23007
23869
  //
23008
23870
  // Given a Byte Sequence as input_bytes, return an ASCII string suitable
@@ -23034,6 +23896,12 @@ function serializeByteSequence(value) {
23034
23896
  return `:${base64encode(value)}:`;
23035
23897
  }
23036
23898
 
23899
+ const INTEGER = 'Integer';
23900
+
23901
+ function isInvalidInt(value) {
23902
+ return value < -999999999999999 || 999999999999999 < value;
23903
+ }
23904
+
23037
23905
  // 4.1.4. Serializing an Integer
23038
23906
  //
23039
23907
  // Given an Integer as input_integer, return an ASCII string suitable
@@ -23099,6 +23967,8 @@ function roundToEven(value, precision) {
23099
23967
  }
23100
23968
  }
23101
23969
 
23970
+ const DECIMAL = 'Decimal';
23971
+
23102
23972
  // 4.1.5. Serializing a Decimal
23103
23973
  //
23104
23974
  // Given a decimal number as input_decimal, return an ASCII string
@@ -23144,6 +24014,8 @@ function serializeDecimal(value) {
23144
24014
 
23145
24015
  const STRING = 'String';
23146
24016
 
24017
+ const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
24018
+
23147
24019
  // 4.1.6. Serializing a String
23148
24020
  //
23149
24021
  // Given a String as input_string, return an ASCII string suitable for
@@ -23179,6 +24051,8 @@ function symbolToStr(symbol) {
23179
24051
  return symbol.description || symbol.toString().slice(7, -1);
23180
24052
  }
23181
24053
 
24054
+ const TOKEN = 'Token';
24055
+
23182
24056
  function serializeToken(token) {
23183
24057
  const value = symbolToStr(token);
23184
24058
  if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
@@ -23246,6 +24120,8 @@ function serializeBareItem(value) {
23246
24120
  }
23247
24121
  }
23248
24122
 
24123
+ const KEY = 'Key';
24124
+
23249
24125
  // 4.1.1.3. Serializing a Key
23250
24126
  //
23251
24127
  // Given a key as input_key, return an ASCII string suitable for use in
@@ -23487,36 +24363,6 @@ function urlToRelativePath(url, base) {
23487
24363
  return toPath.join('/');
23488
24364
  }
23489
24365
 
23490
- /**
23491
- * Generate a random v4 UUID
23492
- *
23493
- * @returns A random v4 UUID
23494
- *
23495
- * @group Utils
23496
- *
23497
- * @beta
23498
- */
23499
- function uuid() {
23500
- try {
23501
- return crypto.randomUUID();
23502
- } catch (error) {
23503
- try {
23504
- const url = URL.createObjectURL(new Blob());
23505
- const uuid = url.toString();
23506
- URL.revokeObjectURL(url);
23507
- return uuid.slice(uuid.lastIndexOf('/') + 1);
23508
- } catch (error) {
23509
- let dt = new Date().getTime();
23510
- const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
23511
- const r = (dt + Math.random() * 16) % 16 | 0;
23512
- dt = Math.floor(dt / 16);
23513
- return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
23514
- });
23515
- return uuid;
23516
- }
23517
- }
23518
- }
23519
-
23520
24366
  const toRounded = value => Math.round(value);
23521
24367
  const toUrlSafe = (value, options) => {
23522
24368
  if (options != null && options.baseUrl) {
@@ -23742,6 +24588,36 @@ function appendCmcdQuery(url, cmcd, options) {
23742
24588
  return `${url}${separator}${query}`;
23743
24589
  }
23744
24590
 
24591
+ /**
24592
+ * Generate a random v4 UUID
24593
+ *
24594
+ * @returns A random v4 UUID
24595
+ *
24596
+ * @group Utils
24597
+ *
24598
+ * @beta
24599
+ */
24600
+ function uuid() {
24601
+ try {
24602
+ return crypto.randomUUID();
24603
+ } catch (error) {
24604
+ try {
24605
+ const url = URL.createObjectURL(new Blob());
24606
+ const uuid = url.toString();
24607
+ URL.revokeObjectURL(url);
24608
+ return uuid.slice(uuid.lastIndexOf('/') + 1);
24609
+ } catch (error) {
24610
+ let dt = new Date().getTime();
24611
+ const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
24612
+ const r = (dt + Math.random() * 16) % 16 | 0;
24613
+ dt = Math.floor(dt / 16);
24614
+ return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
24615
+ });
24616
+ return uuid;
24617
+ }
24618
+ }
24619
+ }
24620
+
23745
24621
  /**
23746
24622
  * Controller to deal with Common Media Client Data (CMCD)
23747
24623
  * @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
@@ -23785,7 +24661,7 @@ class CMCDController {
23785
24661
  su: !this.initialized
23786
24662
  });
23787
24663
  } catch (error) {
23788
- logger.warn('Could not generate manifest CMCD data.', error);
24664
+ this.hls.logger.warn('Could not generate manifest CMCD data.', error);
23789
24665
  }
23790
24666
  };
23791
24667
  /**
@@ -23805,9 +24681,15 @@ class CMCDController {
23805
24681
  data.tb = this.getTopBandwidth(ot) / 1000;
23806
24682
  data.bl = this.getBufferLength(ot);
23807
24683
  }
24684
+ const next = this.getNextFrag(fragment);
24685
+ if (next) {
24686
+ if (next.url && next.url !== fragment.url) {
24687
+ data.nor = next.url;
24688
+ }
24689
+ }
23808
24690
  this.apply(context, data);
23809
24691
  } catch (error) {
23810
- logger.warn('Could not generate segment CMCD data.', error);
24692
+ this.hls.logger.warn('Could not generate segment CMCD data.', error);
23811
24693
  }
23812
24694
  };
23813
24695
  this.hls = hls;
@@ -23897,7 +24779,7 @@ class CMCDController {
23897
24779
  data.su = this.buffering;
23898
24780
  }
23899
24781
 
23900
- // TODO: Implement rtp, nrr, nor, dl
24782
+ // TODO: Implement rtp, nrr, dl
23901
24783
 
23902
24784
  const {
23903
24785
  includeKeys
@@ -23908,15 +24790,28 @@ class CMCDController {
23908
24790
  return acc;
23909
24791
  }, {});
23910
24792
  }
24793
+ const options = {
24794
+ baseUrl: context.url
24795
+ };
23911
24796
  if (this.useHeaders) {
23912
24797
  if (!context.headers) {
23913
24798
  context.headers = {};
23914
24799
  }
23915
- appendCmcdHeaders(context.headers, data);
24800
+ appendCmcdHeaders(context.headers, data, options);
23916
24801
  } else {
23917
- context.url = appendCmcdQuery(context.url, data);
24802
+ context.url = appendCmcdQuery(context.url, data, options);
24803
+ }
24804
+ }
24805
+ getNextFrag(fragment) {
24806
+ var _this$hls$levels$frag;
24807
+ const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
24808
+ if (levelDetails) {
24809
+ const index = fragment.sn - levelDetails.startSN;
24810
+ return levelDetails.fragments[index + 1];
23918
24811
  }
24812
+ return undefined;
23919
24813
  }
24814
+
23920
24815
  /**
23921
24816
  * The CMCD object type.
23922
24817
  */
@@ -24045,10 +24940,10 @@ class CMCDController {
24045
24940
  }
24046
24941
 
24047
24942
  const PATHWAY_PENALTY_DURATION_MS = 300000;
24048
- class ContentSteeringController {
24943
+ class ContentSteeringController extends Logger {
24049
24944
  constructor(hls) {
24945
+ super('content-steering', hls.logger);
24050
24946
  this.hls = void 0;
24051
- this.log = void 0;
24052
24947
  this.loader = null;
24053
24948
  this.uri = null;
24054
24949
  this.pathwayId = '.';
@@ -24063,7 +24958,6 @@ class ContentSteeringController {
24063
24958
  this.subtitleTracks = null;
24064
24959
  this.penalizedPathways = {};
24065
24960
  this.hls = hls;
24066
- this.log = logger.log.bind(logger, `[content-steering]:`);
24067
24961
  this.registerListeners();
24068
24962
  }
24069
24963
  registerListeners() {
@@ -24187,7 +25081,7 @@ class ContentSteeringController {
24187
25081
  errorAction.resolved = this.pathwayId !== errorPathway;
24188
25082
  }
24189
25083
  if (!errorAction.resolved) {
24190
- 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)}`);
25084
+ 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)}`);
24191
25085
  }
24192
25086
  }
24193
25087
  }
@@ -24358,7 +25252,7 @@ class ContentSteeringController {
24358
25252
  onSuccess: (response, stats, context, networkDetails) => {
24359
25253
  this.log(`Loaded steering manifest: "${url}"`);
24360
25254
  const steeringData = response.data;
24361
- if (steeringData.VERSION !== 1) {
25255
+ if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
24362
25256
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
24363
25257
  return;
24364
25258
  }
@@ -25328,7 +26222,7 @@ function timelineConfig() {
25328
26222
  /**
25329
26223
  * @ignore
25330
26224
  */
25331
- function mergeConfig(defaultConfig, userConfig) {
26225
+ function mergeConfig(defaultConfig, userConfig, logger) {
25332
26226
  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
25333
26227
  throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
25334
26228
  }
@@ -25398,7 +26292,7 @@ function deepCpy(obj) {
25398
26292
  /**
25399
26293
  * @ignore
25400
26294
  */
25401
- function enableStreamingMode(config) {
26295
+ function enableStreamingMode(config, logger) {
25402
26296
  const currentLoader = config.loader;
25403
26297
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
25404
26298
  // If a developer has configured their own loader, respect that choice
@@ -25415,10 +26309,9 @@ function enableStreamingMode(config) {
25415
26309
  }
25416
26310
  }
25417
26311
 
25418
- let chromeOrFirefox;
25419
26312
  class LevelController extends BasePlaylistController {
25420
26313
  constructor(hls, contentSteeringController) {
25421
- super(hls, '[level-controller]');
26314
+ super(hls, 'level-controller');
25422
26315
  this._levels = [];
25423
26316
  this._firstLevel = -1;
25424
26317
  this._maxAutoLevel = -1;
@@ -25489,23 +26382,15 @@ class LevelController extends BasePlaylistController {
25489
26382
  let videoCodecFound = false;
25490
26383
  let audioCodecFound = false;
25491
26384
  data.levels.forEach(levelParsed => {
25492
- var _audioCodec, _videoCodec;
26385
+ var _videoCodec;
25493
26386
  const attributes = levelParsed.attrs;
25494
-
25495
- // erase audio codec info if browser does not support mp4a.40.34.
25496
- // demuxer will autodetect codec and fallback to mpeg/audio
25497
26387
  let {
25498
26388
  audioCodec,
25499
26389
  videoCodec
25500
26390
  } = levelParsed;
25501
- if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
25502
- chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
25503
- if (chromeOrFirefox) {
25504
- levelParsed.audioCodec = audioCodec = undefined;
25505
- }
25506
- }
25507
26391
  if (audioCodec) {
25508
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
26392
+ // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
26393
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
25509
26394
  }
25510
26395
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
25511
26396
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -25847,7 +26732,12 @@ class LevelController extends BasePlaylistController {
25847
26732
  if (curLevel.fragmentError === 0) {
25848
26733
  curLevel.loadError = 0;
25849
26734
  }
25850
- this.playlistLoaded(level, data, curLevel.details);
26735
+ // Ignore matching details populated by loading a Media Playlist directly
26736
+ let previousDetails = curLevel.details;
26737
+ if (previousDetails === data.details && previousDetails.advanced) {
26738
+ previousDetails = undefined;
26739
+ }
26740
+ this.playlistLoaded(level, data, previousDetails);
25851
26741
  } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
25852
26742
  // received a delta playlist update that cannot be merged
25853
26743
  details.deltaUpdateFailed = true;
@@ -26091,6 +26981,8 @@ class KeyLoader {
26091
26981
  }
26092
26982
  return this.loadKeyEME(keyInfo, frag);
26093
26983
  case 'AES-128':
26984
+ case 'AES-256':
26985
+ case 'AES-256-CTR':
26094
26986
  return this.loadKeyHTTP(keyInfo, frag);
26095
26987
  default:
26096
26988
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -26228,8 +27120,9 @@ const STALL_MINIMUM_DURATION_MS = 250;
26228
27120
  const MAX_START_GAP_JUMP = 2.0;
26229
27121
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
26230
27122
  const SKIP_BUFFER_RANGE_START = 0.05;
26231
- class GapController {
27123
+ class GapController extends Logger {
26232
27124
  constructor(config, media, fragmentTracker, hls) {
27125
+ super('gap-controller', hls.logger);
26233
27126
  this.config = void 0;
26234
27127
  this.media = null;
26235
27128
  this.fragmentTracker = void 0;
@@ -26239,6 +27132,7 @@ class GapController {
26239
27132
  this.stalled = null;
26240
27133
  this.moved = false;
26241
27134
  this.seeking = false;
27135
+ this.ended = 0;
26242
27136
  this.config = config;
26243
27137
  this.media = media;
26244
27138
  this.fragmentTracker = fragmentTracker;
@@ -26256,7 +27150,7 @@ class GapController {
26256
27150
  *
26257
27151
  * @param lastCurrentTime - Previously read playhead position
26258
27152
  */
26259
- poll(lastCurrentTime, activeFrag) {
27153
+ poll(lastCurrentTime, activeFrag, levelDetails, state) {
26260
27154
  const {
26261
27155
  config,
26262
27156
  media,
@@ -26275,6 +27169,7 @@ class GapController {
26275
27169
 
26276
27170
  // The playhead is moving, no-op
26277
27171
  if (currentTime !== lastCurrentTime) {
27172
+ this.ended = 0;
26278
27173
  this.moved = true;
26279
27174
  if (!seeking) {
26280
27175
  this.nudgeRetry = 0;
@@ -26283,7 +27178,7 @@ class GapController {
26283
27178
  // The playhead is now moving, but was previously stalled
26284
27179
  if (this.stallReported) {
26285
27180
  const _stalledDuration = self.performance.now() - stalled;
26286
- logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
27181
+ this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
26287
27182
  this.stallReported = false;
26288
27183
  }
26289
27184
  this.stalled = null;
@@ -26319,7 +27214,6 @@ class GapController {
26319
27214
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
26320
27215
  // The addition poll gives the browser a chance to jump the gap for us
26321
27216
  if (!this.moved && this.stalled !== null) {
26322
- var _level$details;
26323
27217
  // There is no playable buffer (seeked, waiting for buffer)
26324
27218
  const isBuffered = bufferInfo.len > 0;
26325
27219
  if (!isBuffered && !nextStart) {
@@ -26331,9 +27225,8 @@ class GapController {
26331
27225
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
26332
27226
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
26333
27227
  // that begins over 1 target duration after the video start position.
26334
- const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
26335
- const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
26336
- const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
27228
+ const isLive = !!(levelDetails != null && levelDetails.live);
27229
+ const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
26337
27230
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
26338
27231
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
26339
27232
  if (!media.paused) {
@@ -26351,6 +27244,17 @@ class GapController {
26351
27244
  }
26352
27245
  const stalledDuration = tnow - stalled;
26353
27246
  if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
27247
+ // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
27248
+ if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
27249
+ if (stalledDuration < 1000 || this.ended) {
27250
+ return;
27251
+ }
27252
+ this.ended = currentTime;
27253
+ this.hls.trigger(Events.MEDIA_ENDED, {
27254
+ stalled: true
27255
+ });
27256
+ return;
27257
+ }
26354
27258
  // Report stalling after trying to fix
26355
27259
  this._reportStall(bufferInfo);
26356
27260
  if (!this.media) {
@@ -26394,7 +27298,7 @@ class GapController {
26394
27298
  // needs to cross some sort of threshold covering all source-buffers content
26395
27299
  // to start playing properly.
26396
27300
  if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
26397
- logger.warn('Trying to nudge playhead over buffer-hole');
27301
+ this.warn('Trying to nudge playhead over buffer-hole');
26398
27302
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
26399
27303
  // We only try to jump the hole if it's under the configured size
26400
27304
  // Reset stalled so to rearm watchdog timer
@@ -26418,7 +27322,7 @@ class GapController {
26418
27322
  // Report stalled error once
26419
27323
  this.stallReported = true;
26420
27324
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
26421
- logger.warn(error.message);
27325
+ this.warn(error.message);
26422
27326
  hls.trigger(Events.ERROR, {
26423
27327
  type: ErrorTypes.MEDIA_ERROR,
26424
27328
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -26486,7 +27390,7 @@ class GapController {
26486
27390
  }
26487
27391
  }
26488
27392
  const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
26489
- logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
27393
+ this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
26490
27394
  this.moved = true;
26491
27395
  this.stalled = null;
26492
27396
  media.currentTime = targetTime;
@@ -26527,7 +27431,7 @@ class GapController {
26527
27431
  const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
26528
27432
  // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
26529
27433
  const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
26530
- logger.warn(error.message);
27434
+ this.warn(error.message);
26531
27435
  media.currentTime = targetTime;
26532
27436
  hls.trigger(Events.ERROR, {
26533
27437
  type: ErrorTypes.MEDIA_ERROR,
@@ -26537,7 +27441,7 @@ class GapController {
26537
27441
  });
26538
27442
  } else {
26539
27443
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
26540
- logger.error(error.message);
27444
+ this.error(error.message);
26541
27445
  hls.trigger(Events.ERROR, {
26542
27446
  type: ErrorTypes.MEDIA_ERROR,
26543
27447
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -26552,7 +27456,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
26552
27456
 
26553
27457
  class StreamController extends BaseStreamController {
26554
27458
  constructor(hls, fragmentTracker, keyLoader) {
26555
- super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
27459
+ super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
26556
27460
  this.audioCodecSwap = false;
26557
27461
  this.gapController = null;
26558
27462
  this.level = -1;
@@ -26560,27 +27464,43 @@ class StreamController extends BaseStreamController {
26560
27464
  this.altAudio = false;
26561
27465
  this.audioOnly = false;
26562
27466
  this.fragPlaying = null;
26563
- this.onvplaying = null;
26564
- this.onvseeked = null;
26565
27467
  this.fragLastKbps = 0;
26566
27468
  this.couldBacktrack = false;
26567
27469
  this.backtrackFragment = null;
26568
27470
  this.audioCodecSwitch = false;
26569
27471
  this.videoBuffer = null;
26570
- this._registerListeners();
27472
+ this.onMediaPlaying = () => {
27473
+ // tick to speed up FRAG_CHANGED triggering
27474
+ this.tick();
27475
+ };
27476
+ this.onMediaSeeked = () => {
27477
+ const media = this.media;
27478
+ const currentTime = media ? media.currentTime : null;
27479
+ if (isFiniteNumber(currentTime)) {
27480
+ this.log(`Media seeked to ${currentTime.toFixed(3)}`);
27481
+ }
27482
+
27483
+ // If seeked was issued before buffer was appended do not tick immediately
27484
+ const bufferInfo = this.getMainFwdBufferInfo();
27485
+ if (bufferInfo === null || bufferInfo.len === 0) {
27486
+ this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
27487
+ return;
27488
+ }
27489
+
27490
+ // tick to speed up FRAG_CHANGED triggering
27491
+ this.tick();
27492
+ };
27493
+ this.registerListeners();
26571
27494
  }
26572
- _registerListeners() {
27495
+ registerListeners() {
27496
+ super.registerListeners();
26573
27497
  const {
26574
27498
  hls
26575
27499
  } = this;
26576
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
26577
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
26578
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
26579
27500
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
26580
27501
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
26581
27502
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
26582
27503
  hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26583
- hls.on(Events.ERROR, this.onError, this);
26584
27504
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
26585
27505
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
26586
27506
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -26588,17 +27508,14 @@ class StreamController extends BaseStreamController {
26588
27508
  hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
26589
27509
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
26590
27510
  }
26591
- _unregisterListeners() {
27511
+ unregisterListeners() {
27512
+ super.unregisterListeners();
26592
27513
  const {
26593
27514
  hls
26594
27515
  } = this;
26595
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
26596
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
26597
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
26598
27516
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
26599
27517
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
26600
27518
  hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26601
- hls.off(Events.ERROR, this.onError, this);
26602
27519
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
26603
27520
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
26604
27521
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -26607,7 +27524,9 @@ class StreamController extends BaseStreamController {
26607
27524
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
26608
27525
  }
26609
27526
  onHandlerDestroying() {
26610
- this._unregisterListeners();
27527
+ // @ts-ignore
27528
+ this.onMediaPlaying = this.onMediaSeeked = null;
27529
+ this.unregisterListeners();
26611
27530
  super.onHandlerDestroying();
26612
27531
  }
26613
27532
  startLoad(startPosition) {
@@ -26932,20 +27851,17 @@ class StreamController extends BaseStreamController {
26932
27851
  onMediaAttached(event, data) {
26933
27852
  super.onMediaAttached(event, data);
26934
27853
  const media = data.media;
26935
- this.onvplaying = this.onMediaPlaying.bind(this);
26936
- this.onvseeked = this.onMediaSeeked.bind(this);
26937
- media.addEventListener('playing', this.onvplaying);
26938
- media.addEventListener('seeked', this.onvseeked);
27854
+ media.addEventListener('playing', this.onMediaPlaying);
27855
+ media.addEventListener('seeked', this.onMediaSeeked);
26939
27856
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
26940
27857
  }
26941
27858
  onMediaDetaching() {
26942
27859
  const {
26943
27860
  media
26944
27861
  } = this;
26945
- if (media && this.onvplaying && this.onvseeked) {
26946
- media.removeEventListener('playing', this.onvplaying);
26947
- media.removeEventListener('seeked', this.onvseeked);
26948
- this.onvplaying = this.onvseeked = null;
27862
+ if (media) {
27863
+ media.removeEventListener('playing', this.onMediaPlaying);
27864
+ media.removeEventListener('seeked', this.onMediaSeeked);
26949
27865
  this.videoBuffer = null;
26950
27866
  }
26951
27867
  this.fragPlaying = null;
@@ -26955,27 +27871,6 @@ class StreamController extends BaseStreamController {
26955
27871
  }
26956
27872
  super.onMediaDetaching();
26957
27873
  }
26958
- onMediaPlaying() {
26959
- // tick to speed up FRAG_CHANGED triggering
26960
- this.tick();
26961
- }
26962
- onMediaSeeked() {
26963
- const media = this.media;
26964
- const currentTime = media ? media.currentTime : null;
26965
- if (isFiniteNumber(currentTime)) {
26966
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
26967
- }
26968
-
26969
- // If seeked was issued before buffer was appended do not tick immediately
26970
- const bufferInfo = this.getMainFwdBufferInfo();
26971
- if (bufferInfo === null || bufferInfo.len === 0) {
26972
- this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
26973
- return;
26974
- }
26975
-
26976
- // tick to speed up FRAG_CHANGED triggering
26977
- this.tick();
26978
- }
26979
27874
  onManifestLoading() {
26980
27875
  // reset buffer on manifest loading
26981
27876
  this.log('Trigger BUFFER_RESET');
@@ -27267,8 +28162,10 @@ class StreamController extends BaseStreamController {
27267
28162
  }
27268
28163
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
27269
28164
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
27270
- const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
27271
- gapController.poll(this.lastCurrentTime, activeFrag);
28165
+ const state = this.state;
28166
+ const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
28167
+ const levelDetails = this.getLevelDetails();
28168
+ gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
27272
28169
  }
27273
28170
  this.lastCurrentTime = media.currentTime;
27274
28171
  }
@@ -27706,7 +28603,7 @@ class Hls {
27706
28603
  * Get the video-dev/hls.js package version.
27707
28604
  */
27708
28605
  static get version() {
27709
- return "1.5.6";
28606
+ return "1.5.7-0.canary.10015";
27710
28607
  }
27711
28608
 
27712
28609
  /**
@@ -27769,9 +28666,12 @@ class Hls {
27769
28666
  * The configuration object provided on player instantiation.
27770
28667
  */
27771
28668
  this.userConfig = void 0;
28669
+ /**
28670
+ * The logger functions used by this player instance, configured on player instantiation.
28671
+ */
28672
+ this.logger = void 0;
27772
28673
  this.coreComponents = void 0;
27773
28674
  this.networkControllers = void 0;
27774
- this.started = false;
27775
28675
  this._emitter = new EventEmitter();
27776
28676
  this._autoLevelCapping = -1;
27777
28677
  this._maxHdcpLevel = null;
@@ -27788,11 +28688,11 @@ class Hls {
27788
28688
  this._media = null;
27789
28689
  this.url = null;
27790
28690
  this.triggeringException = void 0;
27791
- enableLogs(userConfig.debug || false, 'Hls instance');
27792
- const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
28691
+ const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
28692
+ const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
27793
28693
  this.userConfig = userConfig;
27794
28694
  if (config.progressive) {
27795
- enableStreamingMode(config);
28695
+ enableStreamingMode(config, logger);
27796
28696
  }
27797
28697
 
27798
28698
  // core controllers and network loaders
@@ -27891,7 +28791,7 @@ class Hls {
27891
28791
  try {
27892
28792
  return this.emit(event, event, eventObject);
27893
28793
  } catch (error) {
27894
- logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
28794
+ this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
27895
28795
  // Prevent recursion in error event handlers that throw #5497
27896
28796
  if (!this.triggeringException) {
27897
28797
  this.triggeringException = true;
@@ -27917,7 +28817,7 @@ class Hls {
27917
28817
  * Dispose of the instance
27918
28818
  */
27919
28819
  destroy() {
27920
- logger.log('destroy');
28820
+ this.logger.log('destroy');
27921
28821
  this.trigger(Events.DESTROYING, undefined);
27922
28822
  this.detachMedia();
27923
28823
  this.removeAllListeners();
@@ -27938,7 +28838,7 @@ class Hls {
27938
28838
  * Attaches Hls.js to a media element
27939
28839
  */
27940
28840
  attachMedia(media) {
27941
- logger.log('attachMedia');
28841
+ this.logger.log('attachMedia');
27942
28842
  this._media = media;
27943
28843
  this.trigger(Events.MEDIA_ATTACHING, {
27944
28844
  media: media
@@ -27949,7 +28849,7 @@ class Hls {
27949
28849
  * Detach Hls.js from the media
27950
28850
  */
27951
28851
  detachMedia() {
27952
- logger.log('detachMedia');
28852
+ this.logger.log('detachMedia');
27953
28853
  this.trigger(Events.MEDIA_DETACHING, undefined);
27954
28854
  this._media = null;
27955
28855
  }
@@ -27966,7 +28866,7 @@ class Hls {
27966
28866
  });
27967
28867
  this._autoLevelCapping = -1;
27968
28868
  this._maxHdcpLevel = null;
27969
- logger.log(`loadSource:${loadingSource}`);
28869
+ this.logger.log(`loadSource:${loadingSource}`);
27970
28870
  if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
27971
28871
  this.detachMedia();
27972
28872
  this.attachMedia(media);
@@ -27985,8 +28885,7 @@ class Hls {
27985
28885
  * Defaults to -1 (None: starts from earliest point)
27986
28886
  */
27987
28887
  startLoad(startPosition = -1) {
27988
- logger.log(`startLoad(${startPosition})`);
27989
- this.started = true;
28888
+ this.logger.log(`startLoad(${startPosition})`);
27990
28889
  this.networkControllers.forEach(controller => {
27991
28890
  controller.startLoad(startPosition);
27992
28891
  });
@@ -27996,34 +28895,31 @@ class Hls {
27996
28895
  * Stop loading of any stream data.
27997
28896
  */
27998
28897
  stopLoad() {
27999
- logger.log('stopLoad');
28000
- this.started = false;
28898
+ this.logger.log('stopLoad');
28001
28899
  this.networkControllers.forEach(controller => {
28002
28900
  controller.stopLoad();
28003
28901
  });
28004
28902
  }
28005
28903
 
28006
28904
  /**
28007
- * Resumes stream controller segment loading if previously started.
28905
+ * Resumes stream controller segment loading after `pauseBuffering` has been called.
28008
28906
  */
28009
28907
  resumeBuffering() {
28010
- if (this.started) {
28011
- this.networkControllers.forEach(controller => {
28012
- if ('fragmentLoader' in controller) {
28013
- controller.startLoad(-1);
28014
- }
28015
- });
28016
- }
28908
+ this.networkControllers.forEach(controller => {
28909
+ if (controller.resumeBuffering) {
28910
+ controller.resumeBuffering();
28911
+ }
28912
+ });
28017
28913
  }
28018
28914
 
28019
28915
  /**
28020
- * Stops stream controller segment loading without changing 'started' state like stopLoad().
28916
+ * Prevents stream controller from loading new segments until `resumeBuffering` is called.
28021
28917
  * This allows for media buffering to be paused without interupting playlist loading.
28022
28918
  */
28023
28919
  pauseBuffering() {
28024
28920
  this.networkControllers.forEach(controller => {
28025
- if ('fragmentLoader' in controller) {
28026
- controller.stopLoad();
28921
+ if (controller.pauseBuffering) {
28922
+ controller.pauseBuffering();
28027
28923
  }
28028
28924
  });
28029
28925
  }
@@ -28032,7 +28928,7 @@ class Hls {
28032
28928
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
28033
28929
  */
28034
28930
  swapAudioCodec() {
28035
- logger.log('swapAudioCodec');
28931
+ this.logger.log('swapAudioCodec');
28036
28932
  this.streamController.swapAudioCodec();
28037
28933
  }
28038
28934
 
@@ -28043,7 +28939,7 @@ class Hls {
28043
28939
  * Automatic recovery of media-errors by this process is configurable.
28044
28940
  */
28045
28941
  recoverMediaError() {
28046
- logger.log('recoverMediaError');
28942
+ this.logger.log('recoverMediaError');
28047
28943
  const media = this._media;
28048
28944
  this.detachMedia();
28049
28945
  if (media) {
@@ -28073,7 +28969,7 @@ class Hls {
28073
28969
  * 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.
28074
28970
  */
28075
28971
  set currentLevel(newLevel) {
28076
- logger.log(`set currentLevel:${newLevel}`);
28972
+ this.logger.log(`set currentLevel:${newLevel}`);
28077
28973
  this.levelController.manualLevel = newLevel;
28078
28974
  this.streamController.immediateLevelSwitch();
28079
28975
  }
@@ -28092,7 +28988,7 @@ class Hls {
28092
28988
  * @param newLevel - Pass -1 for automatic level selection
28093
28989
  */
28094
28990
  set nextLevel(newLevel) {
28095
- logger.log(`set nextLevel:${newLevel}`);
28991
+ this.logger.log(`set nextLevel:${newLevel}`);
28096
28992
  this.levelController.manualLevel = newLevel;
28097
28993
  this.streamController.nextLevelSwitch();
28098
28994
  }
@@ -28111,7 +29007,7 @@ class Hls {
28111
29007
  * @param newLevel - Pass -1 for automatic level selection
28112
29008
  */
28113
29009
  set loadLevel(newLevel) {
28114
- logger.log(`set loadLevel:${newLevel}`);
29010
+ this.logger.log(`set loadLevel:${newLevel}`);
28115
29011
  this.levelController.manualLevel = newLevel;
28116
29012
  }
28117
29013
 
@@ -28142,7 +29038,7 @@ class Hls {
28142
29038
  * Sets "first-level", see getter.
28143
29039
  */
28144
29040
  set firstLevel(newLevel) {
28145
- logger.log(`set firstLevel:${newLevel}`);
29041
+ this.logger.log(`set firstLevel:${newLevel}`);
28146
29042
  this.levelController.firstLevel = newLevel;
28147
29043
  }
28148
29044
 
@@ -28167,7 +29063,7 @@ class Hls {
28167
29063
  * (determined from download of first segment)
28168
29064
  */
28169
29065
  set startLevel(newLevel) {
28170
- logger.log(`set startLevel:${newLevel}`);
29066
+ this.logger.log(`set startLevel:${newLevel}`);
28171
29067
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
28172
29068
  if (newLevel !== -1) {
28173
29069
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -28242,7 +29138,7 @@ class Hls {
28242
29138
  */
28243
29139
  set autoLevelCapping(newLevel) {
28244
29140
  if (this._autoLevelCapping !== newLevel) {
28245
- logger.log(`set autoLevelCapping:${newLevel}`);
29141
+ this.logger.log(`set autoLevelCapping:${newLevel}`);
28246
29142
  this._autoLevelCapping = newLevel;
28247
29143
  this.levelController.checkMaxAutoUpdated();
28248
29144
  }