hls.js 1.5.6-0.canary.9999 → 1.5.6

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 +0 -1
  2. package/dist/hls-demo.js +0 -10
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +1169 -2069
  5. package/dist/hls.js.d.ts +51 -65
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +875 -1158
  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 +709 -993
  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 +869 -1756
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +20 -20
  20. package/src/config.ts +2 -3
  21. package/src/controller/abr-controller.ts +40 -31
  22. package/src/controller/audio-stream-controller.ts +16 -15
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +8 -20
  25. package/src/controller/base-stream-controller.ts +33 -149
  26. package/src/controller/buffer-controller.ts +11 -11
  27. package/src/controller/cap-level-controller.ts +2 -1
  28. package/src/controller/cmcd-controller.ts +6 -27
  29. package/src/controller/content-steering-controller.ts +6 -8
  30. package/src/controller/eme-controller.ts +22 -9
  31. package/src/controller/error-controller.ts +8 -6
  32. package/src/controller/fps-controller.ts +3 -2
  33. package/src/controller/gap-controller.ts +16 -43
  34. package/src/controller/latency-controller.ts +11 -9
  35. package/src/controller/level-controller.ts +18 -12
  36. package/src/controller/stream-controller.ts +34 -27
  37. package/src/controller/subtitle-stream-controller.ts +14 -13
  38. package/src/controller/subtitle-track-controller.ts +3 -5
  39. package/src/controller/timeline-controller.ts +30 -23
  40. package/src/crypt/aes-crypto.ts +2 -21
  41. package/src/crypt/decrypter.ts +18 -32
  42. package/src/crypt/fast-aes-key.ts +5 -24
  43. package/src/demux/audio/adts.ts +4 -9
  44. package/src/demux/sample-aes.ts +0 -2
  45. package/src/demux/transmuxer-interface.ts +12 -4
  46. package/src/demux/transmuxer-worker.ts +4 -4
  47. package/src/demux/transmuxer.ts +3 -16
  48. package/src/demux/tsdemuxer.ts +37 -71
  49. package/src/demux/video/avc-video-parser.ts +119 -208
  50. package/src/demux/video/base-video-parser.ts +2 -134
  51. package/src/demux/video/exp-golomb.ts +208 -0
  52. package/src/events.ts +0 -7
  53. package/src/hls.ts +34 -42
  54. package/src/loader/fragment-loader.ts +2 -9
  55. package/src/loader/key-loader.ts +0 -2
  56. package/src/loader/level-key.ts +9 -10
  57. package/src/loader/playlist-loader.ts +5 -4
  58. package/src/remux/mp4-generator.ts +1 -196
  59. package/src/remux/mp4-remuxer.ts +7 -23
  60. package/src/task-loop.ts +2 -5
  61. package/src/types/component-api.ts +0 -2
  62. package/src/types/demuxer.ts +0 -3
  63. package/src/types/events.ts +0 -4
  64. package/src/utils/codecs.ts +4 -33
  65. package/src/utils/logger.ts +24 -54
  66. package/src/utils/mp4-tools.ts +6 -4
  67. package/src/crypt/decrypter-aes-mode.ts +0 -4
  68. package/src/demux/video/hevc-video-parser.ts +0 -746
  69. package/src/utils/encryption-methods-util.ts +0 -21
package/dist/hls.mjs CHANGED
@@ -256,7 +256,6 @@ let Events = /*#__PURE__*/function (Events) {
256
256
  Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
257
257
  Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
258
258
  Events["MEDIA_DETACHED"] = "hlsMediaDetached";
259
- Events["MEDIA_ENDED"] = "hlsMediaEnded";
260
259
  Events["BUFFER_RESET"] = "hlsBufferReset";
261
260
  Events["BUFFER_CODECS"] = "hlsBufferCodecs";
262
261
  Events["BUFFER_CREATED"] = "hlsBufferCreated";
@@ -370,6 +369,58 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
370
369
  return ErrorDetails;
371
370
  }({});
372
371
 
372
+ const noop = function noop() {};
373
+ const fakeLogger = {
374
+ trace: noop,
375
+ debug: noop,
376
+ log: noop,
377
+ warn: noop,
378
+ info: noop,
379
+ error: noop
380
+ };
381
+ let exportedLogger = fakeLogger;
382
+
383
+ // let lastCallTime;
384
+ // function formatMsgWithTimeInfo(type, msg) {
385
+ // const now = Date.now();
386
+ // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
387
+ // lastCallTime = now;
388
+ // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
389
+ // return msg;
390
+ // }
391
+
392
+ function consolePrintFn(type) {
393
+ const func = self.console[type];
394
+ if (func) {
395
+ return func.bind(self.console, `[${type}] >`);
396
+ }
397
+ return noop;
398
+ }
399
+ function exportLoggerFunctions(debugConfig, ...functions) {
400
+ functions.forEach(function (type) {
401
+ exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
402
+ });
403
+ }
404
+ function enableLogs(debugConfig, id) {
405
+ // check that console is available
406
+ if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
407
+ exportLoggerFunctions(debugConfig,
408
+ // Remove out from list here to hard-disable a log-level
409
+ // 'trace',
410
+ 'debug', 'log', 'info', 'warn', 'error');
411
+ // Some browsers don't allow to use bind on console object anyway
412
+ // fallback to default if needed
413
+ try {
414
+ exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.6"}`);
415
+ } catch (e) {
416
+ exportedLogger = fakeLogger;
417
+ }
418
+ } else {
419
+ exportedLogger = fakeLogger;
420
+ }
421
+ }
422
+ const logger = exportedLogger;
423
+
373
424
  const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
374
425
  const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
375
426
 
@@ -451,79 +502,6 @@ class AttrList {
451
502
  }
452
503
  }
453
504
 
454
- class Logger {
455
- constructor(label, logger) {
456
- this.trace = void 0;
457
- this.debug = void 0;
458
- this.log = void 0;
459
- this.warn = void 0;
460
- this.info = void 0;
461
- this.error = void 0;
462
- const lb = `[${label}]:`;
463
- this.trace = noop;
464
- this.debug = logger.debug.bind(null, lb);
465
- this.log = logger.log.bind(null, lb);
466
- this.warn = logger.warn.bind(null, lb);
467
- this.info = logger.info.bind(null, lb);
468
- this.error = logger.error.bind(null, lb);
469
- }
470
- }
471
- const noop = function noop() {};
472
- const fakeLogger = {
473
- trace: noop,
474
- debug: noop,
475
- log: noop,
476
- warn: noop,
477
- info: noop,
478
- error: noop
479
- };
480
- function createLogger() {
481
- return _extends({}, fakeLogger);
482
- }
483
-
484
- // let lastCallTime;
485
- // function formatMsgWithTimeInfo(type, msg) {
486
- // const now = Date.now();
487
- // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
488
- // lastCallTime = now;
489
- // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
490
- // return msg;
491
- // }
492
-
493
- function consolePrintFn(type, id) {
494
- const func = self.console[type];
495
- return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
496
- }
497
- function getLoggerFn(key, debugConfig, id) {
498
- return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
499
- }
500
- const exportedLogger = createLogger();
501
- function enableLogs(debugConfig, context, id) {
502
- // check that console is available
503
- const newLogger = createLogger();
504
- if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
505
- const keys = [
506
- // Remove out from list here to hard-disable a log-level
507
- // 'trace',
508
- 'debug', 'log', 'info', 'warn', 'error'];
509
- keys.forEach(key => {
510
- newLogger[key] = getLoggerFn(key, debugConfig, id);
511
- });
512
- // Some browsers don't allow to use bind on console object anyway
513
- // fallback to default if needed
514
- try {
515
- newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.6-0.canary.9999"}`);
516
- } catch (e) {
517
- /* log fn threw an exception. All logger methods are no-ops. */
518
- return createLogger();
519
- }
520
- }
521
- // global exported logger uses the log methods from last call to `enableLogs`
522
- _extends(exportedLogger, newLogger);
523
- return newLogger;
524
- }
525
- const logger = exportedLogger;
526
-
527
505
  // Avoid exporting const enum so that these values can be inlined
528
506
 
529
507
  function isDateRangeCueAttribute(attrName) {
@@ -1058,26 +1036,6 @@ function strToUtf8array(str) {
1058
1036
  return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
1059
1037
  }
1060
1038
 
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
-
1081
1039
  /** returns `undefined` is `self` is missing, e.g. in node */
1082
1040
  const optionalSelf = typeof self !== 'undefined' ? self : undefined;
1083
1041
 
@@ -1663,11 +1621,13 @@ function parseSegmentIndex(sidx) {
1663
1621
  let earliestPresentationTime = 0;
1664
1622
  let firstOffset = 0;
1665
1623
  if (version === 0) {
1666
- earliestPresentationTime = readUint32(sidx, index += 4);
1667
- firstOffset = readUint32(sidx, index += 4);
1624
+ earliestPresentationTime = readUint32(sidx, index);
1625
+ firstOffset = readUint32(sidx, index + 4);
1626
+ index += 8;
1668
1627
  } else {
1669
- earliestPresentationTime = readUint64(sidx, index += 8);
1670
- firstOffset = readUint64(sidx, index += 8);
1628
+ earliestPresentationTime = readUint64(sidx, index);
1629
+ firstOffset = readUint64(sidx, index + 8);
1630
+ index += 16;
1671
1631
  }
1672
1632
 
1673
1633
  // skip reserved
@@ -2728,12 +2688,12 @@ class LevelKey {
2728
2688
  this.keyFormatVersions = formatversions;
2729
2689
  this.iv = iv;
2730
2690
  this.encrypted = method ? method !== 'NONE' : false;
2731
- this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
2691
+ this.isCommonEncryption = this.encrypted && method !== 'AES-128';
2732
2692
  }
2733
2693
  isSupported() {
2734
2694
  // If it's Segment encryption or No encryption, just select that key system
2735
2695
  if (this.method) {
2736
- if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
2696
+ if (this.method === 'AES-128' || this.method === 'NONE') {
2737
2697
  return true;
2738
2698
  }
2739
2699
  if (this.keyFormat === 'identity') {
@@ -2755,13 +2715,14 @@ class LevelKey {
2755
2715
  if (!this.encrypted || !this.uri) {
2756
2716
  return null;
2757
2717
  }
2758
- if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
2718
+ if (this.method === 'AES-128' && this.uri && !this.iv) {
2759
2719
  if (typeof sn !== 'number') {
2760
2720
  // We are fetching decryption data for a initialization segment
2761
- // If the segment was encrypted with AES-128/256
2721
+ // If the segment was encrypted with AES-128
2762
2722
  // It must have an IV defined. We cannot substitute the Segment Number in.
2763
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2764
-
2723
+ if (this.method === 'AES-128' && !this.iv) {
2724
+ logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2725
+ }
2765
2726
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
2766
2727
  sn = 0;
2767
2728
  }
@@ -3040,28 +3001,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
3040
3001
  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
3041
3002
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
3042
3003
  }
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
3043
3008
  const codecsToCheck = {
3044
- // Idealy fLaC and Opus would be first (spec-compliant) but
3045
- // some browsers will report that fLaC is supported then fail.
3046
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3047
3009
  flac: ['flac', 'fLaC', 'FLAC'],
3048
- opus: ['opus', 'Opus'],
3049
- // Replace audio codec info if browser does not support mp4a.40.34,
3050
- // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
3051
- 'mp4a.40.34': ['mp3']
3010
+ opus: ['opus', 'Opus']
3052
3011
  }[lowerCaseCodec];
3053
3012
  for (let i = 0; i < codecsToCheck.length; i++) {
3054
- var _getMediaSource;
3055
3013
  if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
3056
3014
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
3057
3015
  return codecsToCheck[i];
3058
- } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
3059
- return '';
3060
3016
  }
3061
3017
  }
3062
3018
  return lowerCaseCodec;
3063
3019
  }
3064
- const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
3020
+ const AUDIO_CODEC_REGEXP = /flac|opus/i;
3065
3021
  function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
3066
3022
  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
3067
3023
  }
@@ -3084,16 +3040,6 @@ function convertAVC1ToAVCOTI(codec) {
3084
3040
  }
3085
3041
  return codec;
3086
3042
  }
3087
- function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
3088
- const MediaSource = getMediaSource(preferManagedMediaSource) || {
3089
- isTypeSupported: () => false
3090
- };
3091
- return {
3092
- mpeg: MediaSource.isTypeSupported('audio/mpeg'),
3093
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
3094
- ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
3095
- };
3096
- }
3097
3043
 
3098
3044
  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;
3099
3045
  const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -3943,10 +3889,10 @@ class PlaylistLoader {
3943
3889
  const loaderContext = loader.context;
3944
3890
  if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
3945
3891
  // same URL can't overlap
3946
- this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
3892
+ logger.trace('[playlist-loader]: playlist request ongoing');
3947
3893
  return;
3948
3894
  }
3949
- this.hls.logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3895
+ logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3950
3896
  loader.abort();
3951
3897
  }
3952
3898
 
@@ -4056,7 +4002,7 @@ class PlaylistLoader {
4056
4002
  // alt audio rendition in which quality levels (main)
4057
4003
  // contains both audio+video. but with mixed audio track not signaled
4058
4004
  if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
4059
- this.hls.logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
4005
+ logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
4060
4006
  audioTracks.unshift({
4061
4007
  type: 'main',
4062
4008
  name: 'main',
@@ -4155,7 +4101,7 @@ class PlaylistLoader {
4155
4101
  message += ` id: ${context.id} group-id: "${context.groupId}"`;
4156
4102
  }
4157
4103
  const error = new Error(message);
4158
- this.hls.logger.warn(`[playlist-loader]: ${message}`);
4104
+ logger.warn(`[playlist-loader]: ${message}`);
4159
4105
  let details = ErrorDetails.UNKNOWN;
4160
4106
  let fatal = false;
4161
4107
  const loader = this.getInternalLoader(context);
@@ -4760,47 +4706,7 @@ class LatencyController {
4760
4706
  this.currentTime = 0;
4761
4707
  this.stallCount = 0;
4762
4708
  this._latency = null;
4763
- this.onTimeupdate = () => {
4764
- const {
4765
- media,
4766
- levelDetails
4767
- } = this;
4768
- if (!media || !levelDetails) {
4769
- return;
4770
- }
4771
- this.currentTime = media.currentTime;
4772
- const latency = this.computeLatency();
4773
- if (latency === null) {
4774
- return;
4775
- }
4776
- this._latency = latency;
4777
-
4778
- // Adapt playbackRate to meet target latency in low-latency mode
4779
- const {
4780
- lowLatencyMode,
4781
- maxLiveSyncPlaybackRate
4782
- } = this.config;
4783
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4784
- return;
4785
- }
4786
- const targetLatency = this.targetLatency;
4787
- if (targetLatency === null) {
4788
- return;
4789
- }
4790
- const distanceFromTarget = latency - targetLatency;
4791
- // Only adjust playbackRate when within one target duration of targetLatency
4792
- // and more than one second from under-buffering.
4793
- // Playback further than one target duration from target can be considered DVR playback.
4794
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4795
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4796
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4797
- const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4798
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4799
- media.playbackRate = Math.min(max, Math.max(1, rate));
4800
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4801
- media.playbackRate = 1;
4802
- }
4803
- };
4709
+ this.timeupdateHandler = () => this.timeupdate();
4804
4710
  this.hls = hls;
4805
4711
  this.config = hls.config;
4806
4712
  this.registerListeners();
@@ -4892,7 +4798,7 @@ class LatencyController {
4892
4798
  this.onMediaDetaching();
4893
4799
  this.levelDetails = null;
4894
4800
  // @ts-ignore
4895
- this.hls = null;
4801
+ this.hls = this.timeupdateHandler = null;
4896
4802
  }
4897
4803
  registerListeners() {
4898
4804
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -4910,11 +4816,11 @@ class LatencyController {
4910
4816
  }
4911
4817
  onMediaAttached(event, data) {
4912
4818
  this.media = data.media;
4913
- this.media.addEventListener('timeupdate', this.onTimeupdate);
4819
+ this.media.addEventListener('timeupdate', this.timeupdateHandler);
4914
4820
  }
4915
4821
  onMediaDetaching() {
4916
4822
  if (this.media) {
4917
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4823
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4918
4824
  this.media = null;
4919
4825
  }
4920
4826
  }
@@ -4928,10 +4834,10 @@ class LatencyController {
4928
4834
  }) {
4929
4835
  this.levelDetails = details;
4930
4836
  if (details.advanced) {
4931
- this.onTimeupdate();
4837
+ this.timeupdate();
4932
4838
  }
4933
4839
  if (!details.live && this.media) {
4934
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4840
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4935
4841
  }
4936
4842
  }
4937
4843
  onError(event, data) {
@@ -4941,7 +4847,48 @@ class LatencyController {
4941
4847
  }
4942
4848
  this.stallCount++;
4943
4849
  if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
4944
- this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
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;
4945
4892
  }
4946
4893
  }
4947
4894
  estimateLiveEdge() {
@@ -5713,13 +5660,18 @@ var ErrorActionFlags = {
5713
5660
  MoveAllAlternatesMatchingHDCP: 2,
5714
5661
  SwitchToSDR: 4
5715
5662
  }; // Reserved for future use
5716
- class ErrorController extends Logger {
5663
+ class ErrorController {
5717
5664
  constructor(hls) {
5718
- super('error-controller', hls.logger);
5719
5665
  this.hls = void 0;
5720
5666
  this.playlistError = 0;
5721
5667
  this.penalizedRenditions = {};
5668
+ this.log = void 0;
5669
+ this.warn = void 0;
5670
+ this.error = void 0;
5722
5671
  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]:`);
5723
5675
  this.registerListeners();
5724
5676
  }
5725
5677
  registerListeners() {
@@ -6071,13 +6023,16 @@ class ErrorController extends Logger {
6071
6023
  }
6072
6024
  }
6073
6025
 
6074
- class BasePlaylistController extends Logger {
6026
+ class BasePlaylistController {
6075
6027
  constructor(hls, logPrefix) {
6076
- super(logPrefix, hls.logger);
6077
6028
  this.hls = void 0;
6078
6029
  this.timer = -1;
6079
6030
  this.requestScheduled = -1;
6080
6031
  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}:`);
6081
6036
  this.hls = hls;
6082
6037
  }
6083
6038
  destroy() {
@@ -6110,7 +6065,7 @@ class BasePlaylistController extends Logger {
6110
6065
  try {
6111
6066
  uri = new self.URL(attr.URI, previous.url).href;
6112
6067
  } catch (error) {
6113
- this.warn(`Could not construct new URL for Rendition Report: ${error}`);
6068
+ logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
6114
6069
  uri = attr.URI || '';
6115
6070
  }
6116
6071
  // Use exact match. Otherwise, the last partial match, if any, will be used
@@ -6197,12 +6152,7 @@ class BasePlaylistController extends Logger {
6197
6152
  const cdnAge = lastAdvanced + details.ageHeader;
6198
6153
  let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
6199
6154
  if (currentGoal > 0) {
6200
- if (cdnAge > details.targetduration * 3) {
6201
- // Omit segment and part directives when the last response was more than 3 target durations ago,
6202
- this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
6203
- msn = undefined;
6204
- part = undefined;
6205
- } else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
6155
+ if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
6206
6156
  // If we attempted to get the next or latest playlist update, but currentGoal increased,
6207
6157
  // then we either can't catchup, or the "age" header cannot be trusted.
6208
6158
  this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
@@ -6874,9 +6824,8 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
6874
6824
  return -1;
6875
6825
  }
6876
6826
 
6877
- class AbrController extends Logger {
6827
+ class AbrController {
6878
6828
  constructor(_hls) {
6879
- super('abr', _hls.logger);
6880
6829
  this.hls = void 0;
6881
6830
  this.lastLevelLoadSec = 0;
6882
6831
  this.lastLoadedFragLevel = -1;
@@ -6990,7 +6939,7 @@ class AbrController extends Logger {
6990
6939
  this.resetEstimator(nextLoadLevelBitrate);
6991
6940
  }
6992
6941
  this.clearTimer();
6993
- this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6942
+ logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6994
6943
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
6995
6944
  Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
6996
6945
  Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
@@ -7010,7 +6959,7 @@ class AbrController extends Logger {
7010
6959
  }
7011
6960
  resetEstimator(abrEwmaDefaultEstimate) {
7012
6961
  if (abrEwmaDefaultEstimate) {
7013
- this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6962
+ logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
7014
6963
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
7015
6964
  }
7016
6965
  this.firstSelection = -1;
@@ -7242,7 +7191,7 @@ class AbrController extends Logger {
7242
7191
  }
7243
7192
  const firstLevel = this.hls.firstLevel;
7244
7193
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
7245
- this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7194
+ logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7246
7195
  return clamped;
7247
7196
  }
7248
7197
  get forcedAutoLevel() {
@@ -7280,8 +7229,7 @@ class AbrController extends Logger {
7280
7229
  return nextABRAutoLevel;
7281
7230
  }
7282
7231
  getAutoLevelKey() {
7283
- var _this$hls$mainForward;
7284
- return `${this.getBwEstimate()}_${(_this$hls$mainForward = this.hls.mainForwardBufferInfo) == null ? void 0 : _this$hls$mainForward.len}`;
7232
+ return `${this.getBwEstimate()}_${this.getStarvationDelay().toFixed(2)}`;
7285
7233
  }
7286
7234
  getNextABRAutoLevel() {
7287
7235
  const {
@@ -7292,18 +7240,12 @@ class AbrController extends Logger {
7292
7240
  const {
7293
7241
  maxAutoLevel,
7294
7242
  config,
7295
- minAutoLevel,
7296
- media
7243
+ minAutoLevel
7297
7244
  } = hls;
7298
7245
  const currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0;
7299
-
7300
- // playbackRate is the absolute value of the playback rate; if media.playbackRate is 0, we use 1 to load as
7301
- // if we're playing back at the normal rate.
7302
- const playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0;
7303
7246
  const avgbw = this.getBwEstimate();
7304
7247
  // bufferStarvationDelay is the wall-clock time left until the playback buffer is exhausted.
7305
- const bufferInfo = hls.mainForwardBufferInfo;
7306
- const bufferStarvationDelay = (bufferInfo ? bufferInfo.len : 0) / playbackRate;
7248
+ const bufferStarvationDelay = this.getStarvationDelay();
7307
7249
  let bwFactor = config.abrBandWidthFactor;
7308
7250
  let bwUpFactor = config.abrBandWidthUpFactor;
7309
7251
 
@@ -7327,13 +7269,13 @@ class AbrController extends Logger {
7327
7269
  // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
7328
7270
  const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
7329
7271
  maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
7330
- this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7272
+ logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7331
7273
  // don't use conservative factor on bitrate test
7332
7274
  bwFactor = bwUpFactor = 1;
7333
7275
  }
7334
7276
  }
7335
7277
  const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
7336
- this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7278
+ logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7337
7279
  if (bestLevel > -1) {
7338
7280
  return bestLevel;
7339
7281
  }
@@ -7346,6 +7288,18 @@ class AbrController extends Logger {
7346
7288
  // or if bitrate is not lower, continue to use loadLevel
7347
7289
  return hls.loadLevel;
7348
7290
  }
7291
+ getStarvationDelay() {
7292
+ const hls = this.hls;
7293
+ const media = hls.media;
7294
+ if (!media) {
7295
+ return Infinity;
7296
+ }
7297
+ // playbackRate is the absolute value of the playback rate; if media.playbackRate is 0, we use 1 to load as
7298
+ // if we're playing back at the normal rate.
7299
+ const playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0;
7300
+ const bufferInfo = hls.mainForwardBufferInfo;
7301
+ return (bufferInfo ? bufferInfo.len : 0) / playbackRate;
7302
+ }
7349
7303
  getBwEstimate() {
7350
7304
  return this.bwEstimator.canEstimate() ? this.bwEstimator.getEstimate() : this.hls.config.abrEwmaDefaultEstimate;
7351
7305
  }
@@ -7395,7 +7349,7 @@ class AbrController extends Logger {
7395
7349
  currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
7396
7350
  currentFrameRate = minFramerate;
7397
7351
  currentBw = Math.max(currentBw, minBitrate);
7398
- this.log(`picked start tier ${JSON.stringify(startTier)}`);
7352
+ logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
7399
7353
  } else {
7400
7354
  currentCodecSet = level == null ? void 0 : level.codecSet;
7401
7355
  currentVideoRange = level == null ? void 0 : level.videoRange;
@@ -7415,15 +7369,18 @@ class AbrController extends Logger {
7415
7369
  if (typeof (mediaCapabilities == null ? void 0 : mediaCapabilities.decodingInfo) === 'function' && requiresMediaCapabilitiesDecodingInfo(levelInfo, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference)) {
7416
7370
  levelInfo.supportedPromise = getMediaDecodingInfoPromise(levelInfo, audioTracksByGroup, mediaCapabilities);
7417
7371
  levelInfo.supportedPromise.then(decodingInfo => {
7372
+ if (!this.hls) {
7373
+ return;
7374
+ }
7418
7375
  levelInfo.supportedResult = decodingInfo;
7419
7376
  const levels = this.hls.levels;
7420
7377
  const index = levels.indexOf(levelInfo);
7421
7378
  if (decodingInfo.error) {
7422
- this.warn(`MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7379
+ logger.warn(`[abr] MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7423
7380
  } else if (!decodingInfo.supported) {
7424
- this.warn(`Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7381
+ logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7425
7382
  if (index > -1 && levels.length > 1) {
7426
- this.log(`Removing unsupported level ${index}`);
7383
+ logger.log(`[abr] Removing unsupported level ${index}`);
7427
7384
  this.hls.removeLevel(index);
7428
7385
  }
7429
7386
  }
@@ -7470,9 +7427,9 @@ class AbrController extends Logger {
7470
7427
  const forcedAutoLevel = this.forcedAutoLevel;
7471
7428
  if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
7472
7429
  if (levelsSkipped.length) {
7473
- 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}`);
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}`);
7474
7431
  }
7475
- 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}`);
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}`);
7476
7433
  }
7477
7434
  if (firstSelection) {
7478
7435
  this.firstSelection = i;
@@ -7526,9 +7483,8 @@ class AbrController extends Logger {
7526
7483
  * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
7527
7484
  * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
7528
7485
  */
7529
- class TaskLoop extends Logger {
7530
- constructor(label, logger) {
7531
- super(label, logger);
7486
+ class TaskLoop {
7487
+ constructor() {
7532
7488
  this._boundTick = void 0;
7533
7489
  this._tickTimer = null;
7534
7490
  this._tickInterval = null;
@@ -8619,8 +8575,8 @@ function createLoaderContext(frag, part = null) {
8619
8575
  var _frag$decryptdata;
8620
8576
  let byteRangeStart = start;
8621
8577
  let byteRangeEnd = end;
8622
- if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
8623
- // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
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,
8624
8580
  // has the unencrypted size specified in the range.
8625
8581
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
8626
8582
  const fragmentLen = end - start;
@@ -8653,9 +8609,6 @@ function createGapLoadError(frag, part) {
8653
8609
  (part ? part : frag).stats.aborted = true;
8654
8610
  return new LoadError(errorData);
8655
8611
  }
8656
- function isMethodFullSegmentAesCbc(method) {
8657
- return method === 'AES-128' || method === 'AES-256';
8658
- }
8659
8612
  class LoadError extends Error {
8660
8613
  constructor(data) {
8661
8614
  super(data.error.message);
@@ -8665,61 +8618,33 @@ class LoadError extends Error {
8665
8618
  }
8666
8619
 
8667
8620
  class AESCrypto {
8668
- constructor(subtle, iv, aesMode) {
8621
+ constructor(subtle, iv) {
8669
8622
  this.subtle = void 0;
8670
8623
  this.aesIV = void 0;
8671
- this.aesMode = void 0;
8672
8624
  this.subtle = subtle;
8673
8625
  this.aesIV = iv;
8674
- this.aesMode = aesMode;
8675
8626
  }
8676
8627
  decrypt(data, key) {
8677
- switch (this.aesMode) {
8678
- case DecrypterAesMode.cbc:
8679
- return this.subtle.decrypt({
8680
- name: 'AES-CBC',
8681
- iv: this.aesIV
8682
- }, key, data);
8683
- case DecrypterAesMode.ctr:
8684
- return this.subtle.decrypt({
8685
- name: 'AES-CTR',
8686
- counter: this.aesIV,
8687
- length: 64
8688
- },
8689
- //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
8690
- key, data);
8691
- default:
8692
- throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
8693
- }
8628
+ return this.subtle.decrypt({
8629
+ name: 'AES-CBC',
8630
+ iv: this.aesIV
8631
+ }, key, data);
8694
8632
  }
8695
8633
  }
8696
8634
 
8697
8635
  class FastAESKey {
8698
- constructor(subtle, key, aesMode) {
8636
+ constructor(subtle, key) {
8699
8637
  this.subtle = void 0;
8700
8638
  this.key = void 0;
8701
- this.aesMode = void 0;
8702
8639
  this.subtle = subtle;
8703
8640
  this.key = key;
8704
- this.aesMode = aesMode;
8705
8641
  }
8706
8642
  expandKey() {
8707
- const subtleAlgoName = getSubtleAlgoName(this.aesMode);
8708
8643
  return this.subtle.importKey('raw', this.key, {
8709
- name: subtleAlgoName
8644
+ name: 'AES-CBC'
8710
8645
  }, false, ['encrypt', 'decrypt']);
8711
8646
  }
8712
8647
  }
8713
- function getSubtleAlgoName(aesMode) {
8714
- switch (aesMode) {
8715
- case DecrypterAesMode.cbc:
8716
- return 'AES-CBC';
8717
- case DecrypterAesMode.ctr:
8718
- return 'AES-CTR';
8719
- default:
8720
- throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
8721
- }
8722
- }
8723
8648
 
8724
8649
  // PKCS7
8725
8650
  function removePadding(array) {
@@ -8969,8 +8894,7 @@ class Decrypter {
8969
8894
  this.currentIV = null;
8970
8895
  this.currentResult = null;
8971
8896
  this.useSoftware = void 0;
8972
- this.enableSoftwareAES = void 0;
8973
- this.enableSoftwareAES = config.enableSoftwareAES;
8897
+ this.useSoftware = config.enableSoftwareAES;
8974
8898
  this.removePKCS7Padding = removePKCS7Padding;
8975
8899
  // built in decryptor expects PKCS7 padding
8976
8900
  if (removePKCS7Padding) {
@@ -8983,7 +8907,9 @@ class Decrypter {
8983
8907
  /* no-op */
8984
8908
  }
8985
8909
  }
8986
- this.useSoftware = this.subtle === null;
8910
+ if (this.subtle === null) {
8911
+ this.useSoftware = true;
8912
+ }
8987
8913
  }
8988
8914
  destroy() {
8989
8915
  this.subtle = null;
@@ -9021,10 +8947,10 @@ class Decrypter {
9021
8947
  this.softwareDecrypter = null;
9022
8948
  }
9023
8949
  }
9024
- decrypt(data, key, iv, aesMode) {
8950
+ decrypt(data, key, iv) {
9025
8951
  if (this.useSoftware) {
9026
8952
  return new Promise((resolve, reject) => {
9027
- this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
8953
+ this.softwareDecrypt(new Uint8Array(data), key, iv);
9028
8954
  const decryptResult = this.flush();
9029
8955
  if (decryptResult) {
9030
8956
  resolve(decryptResult.buffer);
@@ -9033,21 +8959,17 @@ class Decrypter {
9033
8959
  }
9034
8960
  });
9035
8961
  }
9036
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
8962
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
9037
8963
  }
9038
8964
 
9039
8965
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
9040
8966
  // data is handled in the flush() call
9041
- softwareDecrypt(data, key, iv, aesMode) {
8967
+ softwareDecrypt(data, key, iv) {
9042
8968
  const {
9043
8969
  currentIV,
9044
8970
  currentResult,
9045
8971
  remainderData
9046
8972
  } = this;
9047
- if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
9048
- logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
9049
- return null;
9050
- }
9051
8973
  this.logOnce('JS AES decrypt');
9052
8974
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
9053
8975
  // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
@@ -9080,11 +9002,11 @@ class Decrypter {
9080
9002
  }
9081
9003
  return result;
9082
9004
  }
9083
- webCryptoDecrypt(data, key, iv, aesMode) {
9005
+ webCryptoDecrypt(data, key, iv) {
9084
9006
  const subtle = this.subtle;
9085
9007
  if (this.key !== key || !this.fastAesKey) {
9086
9008
  this.key = key;
9087
- this.fastAesKey = new FastAESKey(subtle, key, aesMode);
9009
+ this.fastAesKey = new FastAESKey(subtle, key);
9088
9010
  }
9089
9011
  return this.fastAesKey.expandKey().then(aesKey => {
9090
9012
  // decrypt using web crypto
@@ -9092,25 +9014,22 @@ class Decrypter {
9092
9014
  return Promise.reject(new Error('web crypto not initialized'));
9093
9015
  }
9094
9016
  this.logOnce('WebCrypto AES decrypt');
9095
- const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
9017
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv));
9096
9018
  return crypto.decrypt(data.buffer, aesKey);
9097
9019
  }).catch(err => {
9098
9020
  logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
9099
- return this.onWebCryptoError(data, key, iv, aesMode);
9021
+ return this.onWebCryptoError(data, key, iv);
9100
9022
  });
9101
9023
  }
9102
- onWebCryptoError(data, key, iv, aesMode) {
9103
- const enableSoftwareAES = this.enableSoftwareAES;
9104
- if (enableSoftwareAES) {
9105
- this.useSoftware = true;
9106
- this.logEnabled = true;
9107
- this.softwareDecrypt(data, key, iv, aesMode);
9108
- const decryptResult = this.flush();
9109
- if (decryptResult) {
9110
- return decryptResult.buffer;
9111
- }
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;
9112
9031
  }
9113
- throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
9032
+ throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
9114
9033
  }
9115
9034
  getValidChunk(data) {
9116
9035
  let currentChunk = data;
@@ -9161,7 +9080,7 @@ const State = {
9161
9080
  };
9162
9081
  class BaseStreamController extends TaskLoop {
9163
9082
  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
9164
- super(logPrefix, hls.logger);
9083
+ super();
9165
9084
  this.hls = void 0;
9166
9085
  this.fragPrevious = null;
9167
9086
  this.fragCurrent = null;
@@ -9186,98 +9105,22 @@ class BaseStreamController extends TaskLoop {
9186
9105
  this.startFragRequested = false;
9187
9106
  this.decrypter = void 0;
9188
9107
  this.initPTS = [];
9189
- this.buffering = true;
9190
- this.loadingParts = false;
9191
- this.onMediaSeeking = () => {
9192
- const {
9193
- config,
9194
- fragCurrent,
9195
- media,
9196
- mediaBuffer,
9197
- state
9198
- } = this;
9199
- const currentTime = media ? media.currentTime : 0;
9200
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9201
- this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9202
- if (this.state === State.ENDED) {
9203
- this.resetLoadingState();
9204
- } else if (fragCurrent) {
9205
- // Seeking while frag load is in progress
9206
- const tolerance = config.maxFragLookUpTolerance;
9207
- const fragStartOffset = fragCurrent.start - tolerance;
9208
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9209
- // if seeking out of buffered range or into new one
9210
- if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9211
- const pastFragment = currentTime > fragEndOffset;
9212
- // if the seek position is outside the current fragment range
9213
- if (currentTime < fragStartOffset || pastFragment) {
9214
- if (pastFragment && fragCurrent.loader) {
9215
- this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9216
- fragCurrent.abortRequests();
9217
- this.resetLoadingState();
9218
- }
9219
- this.fragPrevious = null;
9220
- }
9221
- }
9222
- }
9223
- if (media) {
9224
- // Remove gap fragments
9225
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9226
- this.lastCurrentTime = currentTime;
9227
- if (!this.loadingParts) {
9228
- const bufferEnd = Math.max(bufferInfo.end, currentTime);
9229
- const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
9230
- if (shouldLoadParts) {
9231
- this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
9232
- this.loadingParts = shouldLoadParts;
9233
- }
9234
- }
9235
- }
9236
-
9237
- // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9238
- if (!this.loadedmetadata && !bufferInfo.len) {
9239
- this.nextLoadPosition = this.startPosition = currentTime;
9240
- }
9241
-
9242
- // Async tick to speed up processing
9243
- this.tickImmediate();
9244
- };
9245
- this.onMediaEnded = () => {
9246
- // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9247
- this.startPosition = this.lastCurrentTime = 0;
9248
- if (this.playlistType === PlaylistLevelType.MAIN) {
9249
- this.hls.trigger(Events.MEDIA_ENDED, {
9250
- stalled: false
9251
- });
9252
- }
9253
- };
9108
+ this.onvseeking = null;
9109
+ this.onvended = null;
9110
+ this.logPrefix = '';
9111
+ this.log = void 0;
9112
+ this.warn = void 0;
9254
9113
  this.playlistType = playlistType;
9114
+ this.logPrefix = logPrefix;
9115
+ this.log = logger.log.bind(logger, `${logPrefix}:`);
9116
+ this.warn = logger.warn.bind(logger, `${logPrefix}:`);
9255
9117
  this.hls = hls;
9256
9118
  this.fragmentLoader = new FragmentLoader(hls.config);
9257
9119
  this.keyLoader = keyLoader;
9258
9120
  this.fragmentTracker = fragmentTracker;
9259
9121
  this.config = hls.config;
9260
9122
  this.decrypter = new Decrypter(hls.config);
9261
- }
9262
- registerListeners() {
9263
- const {
9264
- hls
9265
- } = this;
9266
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
9267
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
9268
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
9269
9123
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9270
- hls.on(Events.ERROR, this.onError, this);
9271
- }
9272
- unregisterListeners() {
9273
- const {
9274
- hls
9275
- } = this;
9276
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
9277
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
9278
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
9279
- hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9280
- hls.off(Events.ERROR, this.onError, this);
9281
9124
  }
9282
9125
  doTick() {
9283
9126
  this.onTickEnd();
@@ -9301,12 +9144,6 @@ class BaseStreamController extends TaskLoop {
9301
9144
  this.clearNextTick();
9302
9145
  this.state = State.STOPPED;
9303
9146
  }
9304
- pauseBuffering() {
9305
- this.buffering = false;
9306
- }
9307
- resumeBuffering() {
9308
- this.buffering = true;
9309
- }
9310
9147
  _streamEnded(bufferInfo, levelDetails) {
9311
9148
  // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
9312
9149
  // of nothing loading/loaded return false
@@ -9337,8 +9174,10 @@ class BaseStreamController extends TaskLoop {
9337
9174
  }
9338
9175
  onMediaAttached(event, data) {
9339
9176
  const media = this.media = this.mediaBuffer = data.media;
9340
- media.addEventListener('seeking', this.onMediaSeeking);
9341
- media.addEventListener('ended', this.onMediaEnded);
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);
9342
9181
  const config = this.config;
9343
9182
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
9344
9183
  this.startLoad(config.startPosition);
@@ -9352,9 +9191,10 @@ class BaseStreamController extends TaskLoop {
9352
9191
  }
9353
9192
 
9354
9193
  // remove video listeners
9355
- if (media) {
9356
- media.removeEventListener('seeking', this.onMediaSeeking);
9357
- media.removeEventListener('ended', this.onMediaEnded);
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;
9358
9198
  }
9359
9199
  if (this.keyLoader) {
9360
9200
  this.keyLoader.detach();
@@ -9364,8 +9204,56 @@ class BaseStreamController extends TaskLoop {
9364
9204
  this.fragmentTracker.removeAllFragments();
9365
9205
  this.stopLoad();
9366
9206
  }
9367
- onManifestLoading() {}
9368
- onError(event, data) {}
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
+ }
9369
9257
  onManifestLoaded(event, data) {
9370
9258
  this.startTimeOffset = data.startTimeOffset;
9371
9259
  this.initPTS = [];
@@ -9375,7 +9263,7 @@ class BaseStreamController extends TaskLoop {
9375
9263
  this.stopLoad();
9376
9264
  super.onHandlerDestroying();
9377
9265
  // @ts-ignore
9378
- this.hls = this.onMediaSeeking = this.onMediaEnded = null;
9266
+ this.hls = null;
9379
9267
  }
9380
9268
  onHandlerDestroyed() {
9381
9269
  this.state = State.STOPPED;
@@ -9506,10 +9394,10 @@ class BaseStreamController extends TaskLoop {
9506
9394
  const decryptData = frag.decryptdata;
9507
9395
 
9508
9396
  // check to see if the payload needs to be decrypted
9509
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
9397
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
9510
9398
  const startTime = self.performance.now();
9511
9399
  // decrypt init segment data
9512
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
9400
+ return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
9513
9401
  hls.trigger(Events.ERROR, {
9514
9402
  type: ErrorTypes.MEDIA_ERROR,
9515
9403
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -9621,7 +9509,7 @@ class BaseStreamController extends TaskLoop {
9621
9509
  }
9622
9510
  let keyLoadingPromise = null;
9623
9511
  if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
9624
- this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
9512
+ this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
9625
9513
  this.state = State.KEY_LOADING;
9626
9514
  this.fragCurrent = frag;
9627
9515
  keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
@@ -9642,16 +9530,8 @@ class BaseStreamController extends TaskLoop {
9642
9530
  } else if (!frag.encrypted && details.encryptedFragments.length) {
9643
9531
  this.keyLoader.loadClear(frag, details.encryptedFragments);
9644
9532
  }
9645
- const fragPrevious = this.fragPrevious;
9646
- if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
9647
- const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
9648
- if (shouldLoadParts !== this.loadingParts) {
9649
- this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
9650
- this.loadingParts = shouldLoadParts;
9651
- }
9652
- }
9653
9533
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
9654
- if (this.loadingParts && frag.sn !== 'initSegment') {
9534
+ if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
9655
9535
  const partList = details.partList;
9656
9536
  if (partList && progressCallback) {
9657
9537
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -9660,7 +9540,7 @@ class BaseStreamController extends TaskLoop {
9660
9540
  const partIndex = this.getNextPart(partList, frag, targetBufferTime);
9661
9541
  if (partIndex > -1) {
9662
9542
  const part = partList[partIndex];
9663
- 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))}`);
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))}`);
9664
9544
  this.nextLoadPosition = part.start + part.duration;
9665
9545
  this.state = State.FRAG_LOADING;
9666
9546
  let _result;
@@ -9689,14 +9569,7 @@ class BaseStreamController extends TaskLoop {
9689
9569
  }
9690
9570
  }
9691
9571
  }
9692
- if (frag.sn !== 'initSegment' && this.loadingParts) {
9693
- this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
9694
- this.loadingParts = false;
9695
- } else if (!frag.url) {
9696
- // Selected fragment hint for part but not loading parts
9697
- return Promise.resolve(null);
9698
- }
9699
- 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))}`);
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))}`);
9700
9573
  // Don't update nextLoadPosition for fragments which are not buffered
9701
9574
  if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
9702
9575
  this.nextLoadPosition = frag.start + frag.duration;
@@ -9794,36 +9667,8 @@ class BaseStreamController extends TaskLoop {
9794
9667
  if (part) {
9795
9668
  part.stats.parsing.end = now;
9796
9669
  }
9797
- // See if part loading should be disabled/enabled based on buffer and playback position.
9798
- if (frag.sn !== 'initSegment') {
9799
- const levelDetails = this.getLevelDetails();
9800
- const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
9801
- const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
9802
- if (shouldLoadParts !== this.loadingParts) {
9803
- this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
9804
- this.loadingParts = shouldLoadParts;
9805
- }
9806
- }
9807
9670
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
9808
9671
  }
9809
- shouldLoadParts(details, bufferEnd) {
9810
- if (this.config.lowLatencyMode) {
9811
- if (!details) {
9812
- return this.loadingParts;
9813
- }
9814
- if (details != null && details.partList) {
9815
- var _details$fragmentHint;
9816
- // Buffer must be ahead of first part + duration of parts after last segment
9817
- // and playback must be at or past segment adjacent to part list
9818
- const firstPart = details.partList[0];
9819
- const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
9820
- if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
9821
- return true;
9822
- }
9823
- }
9824
- }
9825
- return false;
9826
- }
9827
9672
  getCurrentContext(chunkMeta) {
9828
9673
  const {
9829
9674
  levels,
@@ -9972,8 +9817,7 @@ class BaseStreamController extends TaskLoop {
9972
9817
  config
9973
9818
  } = this;
9974
9819
  const start = fragments[0].start;
9975
- const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
9976
- let frag = null;
9820
+ let frag;
9977
9821
  if (levelDetails.live) {
9978
9822
  const initialLiveManifestSize = config.initialLiveManifestSize;
9979
9823
  if (fragLen < initialLiveManifestSize) {
@@ -9985,10 +9829,6 @@ class BaseStreamController extends TaskLoop {
9985
9829
  // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
9986
9830
  // we get the fragment matching that start time
9987
9831
  if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
9988
- if (canLoadParts && !this.loadingParts) {
9989
- this.log(`LL-Part loading ON for initial live fragment`);
9990
- this.loadingParts = true;
9991
- }
9992
9832
  frag = this.getInitialLiveFragment(levelDetails, fragments);
9993
9833
  this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
9994
9834
  }
@@ -9999,7 +9839,7 @@ class BaseStreamController extends TaskLoop {
9999
9839
 
10000
9840
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
10001
9841
  if (!frag) {
10002
- const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
9842
+ const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
10003
9843
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
10004
9844
  }
10005
9845
  return this.mapToInitFragWhenRequired(frag);
@@ -10121,7 +9961,7 @@ class BaseStreamController extends TaskLoop {
10121
9961
  } = levelDetails;
10122
9962
  const tolerance = config.maxFragLookUpTolerance;
10123
9963
  const partList = levelDetails.partList;
10124
- const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
9964
+ const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
10125
9965
  if (loadingParts && fragmentHint && !this.bitrateTest) {
10126
9966
  // Include incomplete fragment with parts at end
10127
9967
  fragments = fragments.concat(fragmentHint);
@@ -10314,7 +10154,7 @@ class BaseStreamController extends TaskLoop {
10314
10154
  errorAction.resolved = true;
10315
10155
  }
10316
10156
  } else {
10317
- this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10157
+ logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10318
10158
  return;
10319
10159
  }
10320
10160
  } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
@@ -10723,7 +10563,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
10723
10563
  */
10724
10564
  function getAudioConfig(observer, data, offset, audioCodec) {
10725
10565
  let adtsObjectType;
10726
- let originalAdtsObjectType;
10727
10566
  let adtsExtensionSamplingIndex;
10728
10567
  let adtsChannelConfig;
10729
10568
  let config;
@@ -10731,7 +10570,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10731
10570
  const manifestCodec = audioCodec;
10732
10571
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
10733
10572
  // byte 2
10734
- adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10573
+ adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10735
10574
  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
10736
10575
  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
10737
10576
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
@@ -10748,8 +10587,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10748
10587
  // byte 3
10749
10588
  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
10750
10589
  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
10751
- // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
10752
- if (/firefox|palemoon/i.test(userAgent)) {
10590
+ // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
10591
+ if (/firefox/i.test(userAgent)) {
10753
10592
  if (adtsSamplingIndex >= 6) {
10754
10593
  adtsObjectType = 5;
10755
10594
  config = new Array(4);
@@ -10843,7 +10682,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10843
10682
  samplerate: adtsSamplingRates[adtsSamplingIndex],
10844
10683
  channelCount: adtsChannelConfig,
10845
10684
  codec: 'mp4a.40.' + adtsObjectType,
10846
- parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
10847
10685
  manifestCodec
10848
10686
  };
10849
10687
  }
@@ -10898,8 +10736,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
10898
10736
  track.channelCount = config.channelCount;
10899
10737
  track.codec = config.codec;
10900
10738
  track.manifestCodec = config.manifestCodec;
10901
- track.parsedCodec = config.parsedCodec;
10902
- logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10739
+ logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10903
10740
  }
10904
10741
  }
10905
10742
  function getFrameDuration(samplerate) {
@@ -11490,110 +11327,6 @@ class BaseVideoParser {
11490
11327
  logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
11491
11328
  }
11492
11329
  }
11493
- parseNALu(track, array) {
11494
- const len = array.byteLength;
11495
- let state = track.naluState || 0;
11496
- const lastState = state;
11497
- const units = [];
11498
- let i = 0;
11499
- let value;
11500
- let overflow;
11501
- let unitType;
11502
- let lastUnitStart = -1;
11503
- let lastUnitType = 0;
11504
- // logger.log('PES:' + Hex.hexDump(array));
11505
-
11506
- if (state === -1) {
11507
- // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
11508
- lastUnitStart = 0;
11509
- // NALu type is value read from offset 0
11510
- lastUnitType = this.getNALuType(array, 0);
11511
- state = 0;
11512
- i = 1;
11513
- }
11514
- while (i < len) {
11515
- value = array[i++];
11516
- // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
11517
- if (!state) {
11518
- state = value ? 0 : 1;
11519
- continue;
11520
- }
11521
- if (state === 1) {
11522
- state = value ? 0 : 2;
11523
- continue;
11524
- }
11525
- // here we have state either equal to 2 or 3
11526
- if (!value) {
11527
- state = 3;
11528
- } else if (value === 1) {
11529
- overflow = i - state - 1;
11530
- if (lastUnitStart >= 0) {
11531
- const unit = {
11532
- data: array.subarray(lastUnitStart, overflow),
11533
- type: lastUnitType
11534
- };
11535
- // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
11536
- units.push(unit);
11537
- } else {
11538
- // lastUnitStart is undefined => this is the first start code found in this PES packet
11539
- // first check if start code delimiter is overlapping between 2 PES packets,
11540
- // ie it started in last packet (lastState not zero)
11541
- // and ended at the beginning of this PES packet (i <= 4 - lastState)
11542
- const lastUnit = this.getLastNalUnit(track.samples);
11543
- if (lastUnit) {
11544
- if (lastState && i <= 4 - lastState) {
11545
- // start delimiter overlapping between PES packets
11546
- // strip start delimiter bytes from the end of last NAL unit
11547
- // check if lastUnit had a state different from zero
11548
- if (lastUnit.state) {
11549
- // strip last bytes
11550
- lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
11551
- }
11552
- }
11553
- // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
11554
-
11555
- if (overflow > 0) {
11556
- // logger.log('first NALU found with overflow:' + overflow);
11557
- lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
11558
- lastUnit.state = 0;
11559
- }
11560
- }
11561
- }
11562
- // check if we can read unit type
11563
- if (i < len) {
11564
- unitType = this.getNALuType(array, i);
11565
- // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
11566
- lastUnitStart = i;
11567
- lastUnitType = unitType;
11568
- state = 0;
11569
- } else {
11570
- // not enough byte to read unit type. let's read it on next PES parsing
11571
- state = -1;
11572
- }
11573
- } else {
11574
- state = 0;
11575
- }
11576
- }
11577
- if (lastUnitStart >= 0 && state >= 0) {
11578
- const unit = {
11579
- data: array.subarray(lastUnitStart, len),
11580
- type: lastUnitType,
11581
- state: state
11582
- };
11583
- units.push(unit);
11584
- // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
11585
- }
11586
- // no NALu found
11587
- if (units.length === 0) {
11588
- // append pes.data to previous NAL unit
11589
- const lastUnit = this.getLastNalUnit(track.samples);
11590
- if (lastUnit) {
11591
- lastUnit.data = appendUint8Array(lastUnit.data, array);
11592
- }
11593
- }
11594
- track.naluState = state;
11595
- return units;
11596
- }
11597
11330
  }
11598
11331
 
11599
11332
  /**
@@ -11736,171 +11469,21 @@ class ExpGolomb {
11736
11469
  readUInt() {
11737
11470
  return this.readBits(32);
11738
11471
  }
11739
- }
11740
-
11741
- class AvcVideoParser extends BaseVideoParser {
11742
- parsePES(track, textTrack, pes, last, duration) {
11743
- const units = this.parseNALu(track, pes.data);
11744
- let VideoSample = this.VideoSample;
11745
- let push;
11746
- let spsfound = false;
11747
- // free pes.data to save up some memory
11748
- pes.data = null;
11749
-
11750
- // if new NAL units found and last sample still there, let's push ...
11751
- // this helps parsing streams with missing AUD (only do this if AUD never found)
11752
- if (VideoSample && units.length && !track.audFound) {
11753
- this.pushAccessUnit(VideoSample, track);
11754
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
11755
- }
11756
- units.forEach(unit => {
11757
- var _VideoSample2;
11758
- switch (unit.type) {
11759
- // NDR
11760
- case 1:
11761
- {
11762
- let iskey = false;
11763
- push = true;
11764
- const data = unit.data;
11765
- // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
11766
- if (spsfound && data.length > 4) {
11767
- // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
11768
- const sliceType = this.readSliceType(data);
11769
- // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
11770
- // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
11771
- // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
11772
- // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
11773
- // if (sliceType === 2 || sliceType === 7) {
11774
- if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
11775
- iskey = true;
11776
- }
11777
- }
11778
- if (iskey) {
11779
- var _VideoSample;
11780
- // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
11781
- if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
11782
- this.pushAccessUnit(VideoSample, track);
11783
- VideoSample = this.VideoSample = null;
11784
- }
11785
- }
11786
- if (!VideoSample) {
11787
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11788
- }
11789
- VideoSample.frame = true;
11790
- VideoSample.key = iskey;
11791
- break;
11792
- // IDR
11793
- }
11794
- case 5:
11795
- push = true;
11796
- // handle PES not starting with AUD
11797
- // if we have frame data already, that cannot belong to the same frame, so force a push
11798
- if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
11799
- this.pushAccessUnit(VideoSample, track);
11800
- VideoSample = this.VideoSample = null;
11801
- }
11802
- if (!VideoSample) {
11803
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11804
- }
11805
- VideoSample.key = true;
11806
- VideoSample.frame = true;
11807
- break;
11808
- // SEI
11809
- case 6:
11810
- {
11811
- push = true;
11812
- parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
11813
- break;
11814
- // SPS
11815
- }
11816
- case 7:
11817
- {
11818
- var _track$pixelRatio, _track$pixelRatio2;
11819
- push = true;
11820
- spsfound = true;
11821
- const sps = unit.data;
11822
- const config = this.readSPS(sps);
11823
- 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]) {
11824
- track.width = config.width;
11825
- track.height = config.height;
11826
- track.pixelRatio = config.pixelRatio;
11827
- track.sps = [sps];
11828
- track.duration = duration;
11829
- const codecarray = sps.subarray(1, 4);
11830
- let codecstring = 'avc1.';
11831
- for (let i = 0; i < 3; i++) {
11832
- let h = codecarray[i].toString(16);
11833
- if (h.length < 2) {
11834
- h = '0' + h;
11835
- }
11836
- codecstring += h;
11837
- }
11838
- track.codec = codecstring;
11839
- }
11840
- break;
11841
- }
11842
- // PPS
11843
- case 8:
11844
- push = true;
11845
- track.pps = [unit.data];
11846
- break;
11847
- // AUD
11848
- case 9:
11849
- push = true;
11850
- track.audFound = true;
11851
- if (VideoSample) {
11852
- this.pushAccessUnit(VideoSample, track);
11853
- }
11854
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
11855
- break;
11856
- // Filler Data
11857
- case 12:
11858
- push = true;
11859
- break;
11860
- default:
11861
- push = false;
11862
- if (VideoSample) {
11863
- VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
11864
- }
11865
- break;
11866
- }
11867
- if (VideoSample && push) {
11868
- const units = VideoSample.units;
11869
- units.push(unit);
11870
- }
11871
- });
11872
- // if last PES packet, push samples
11873
- if (last && VideoSample) {
11874
- this.pushAccessUnit(VideoSample, track);
11875
- this.VideoSample = null;
11876
- }
11877
- }
11878
- getNALuType(data, offset) {
11879
- return data[offset] & 0x1f;
11880
- }
11881
- readSliceType(data) {
11882
- const eg = new ExpGolomb(data);
11883
- // skip NALu type
11884
- eg.readUByte();
11885
- // discard first_mb_in_slice
11886
- eg.readUEG();
11887
- // return slice_type
11888
- return eg.readUEG();
11889
- }
11890
11472
 
11891
11473
  /**
11892
- * The scaling list is optionally transmitted as part of a sequence parameter
11474
+ * Advance the ExpGolomb decoder past a scaling list. The scaling
11475
+ * list is optionally transmitted as part of a sequence parameter
11893
11476
  * set and is not relevant to transmuxing.
11894
11477
  * @param count the number of entries in this scaling list
11895
11478
  * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
11896
11479
  */
11897
- skipScalingList(count, reader) {
11480
+ skipScalingList(count) {
11898
11481
  let lastScale = 8;
11899
11482
  let nextScale = 8;
11900
11483
  let deltaScale;
11901
11484
  for (let j = 0; j < count; j++) {
11902
11485
  if (nextScale !== 0) {
11903
- deltaScale = reader.readEG();
11486
+ deltaScale = this.readEG();
11904
11487
  nextScale = (lastScale + deltaScale + 256) % 256;
11905
11488
  }
11906
11489
  lastScale = nextScale === 0 ? lastScale : nextScale;
@@ -11915,8 +11498,7 @@ class AvcVideoParser extends BaseVideoParser {
11915
11498
  * sequence parameter set, including the dimensions of the
11916
11499
  * associated video frames.
11917
11500
  */
11918
- readSPS(sps) {
11919
- const eg = new ExpGolomb(sps);
11501
+ readSPS() {
11920
11502
  let frameCropLeftOffset = 0;
11921
11503
  let frameCropRightOffset = 0;
11922
11504
  let frameCropTopOffset = 0;
@@ -11924,13 +11506,13 @@ class AvcVideoParser extends BaseVideoParser {
11924
11506
  let numRefFramesInPicOrderCntCycle;
11925
11507
  let scalingListCount;
11926
11508
  let i;
11927
- const readUByte = eg.readUByte.bind(eg);
11928
- const readBits = eg.readBits.bind(eg);
11929
- const readUEG = eg.readUEG.bind(eg);
11930
- const readBoolean = eg.readBoolean.bind(eg);
11931
- const skipBits = eg.skipBits.bind(eg);
11932
- const skipEG = eg.skipEG.bind(eg);
11933
- const skipUEG = eg.skipUEG.bind(eg);
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);
11934
11516
  const skipScalingList = this.skipScalingList.bind(this);
11935
11517
  readUByte();
11936
11518
  const profileIdc = readUByte(); // profile_idc
@@ -11955,9 +11537,9 @@ class AvcVideoParser extends BaseVideoParser {
11955
11537
  if (readBoolean()) {
11956
11538
  // seq_scaling_list_present_flag[ i ]
11957
11539
  if (i < 6) {
11958
- skipScalingList(16, eg);
11540
+ skipScalingList(16);
11959
11541
  } else {
11960
- skipScalingList(64, eg);
11542
+ skipScalingList(64);
11961
11543
  }
11962
11544
  }
11963
11545
  }
@@ -12062,15 +11644,19 @@ class AvcVideoParser extends BaseVideoParser {
12062
11644
  pixelRatio: pixelRatio
12063
11645
  };
12064
11646
  }
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
+ }
12065
11655
  }
12066
11656
 
12067
- class HevcVideoParser extends BaseVideoParser {
12068
- constructor(...args) {
12069
- super(...args);
12070
- this.initVPS = null;
12071
- }
12072
- parsePES(track, textTrack, pes, last, duration) {
12073
- const units = this.parseNALu(track, pes.data);
11657
+ class AvcVideoParser extends BaseVideoParser {
11658
+ parseAVCPES(track, textTrack, pes, last, duration) {
11659
+ const units = this.parseAVCNALu(track, pes.data);
12074
11660
  let VideoSample = this.VideoSample;
12075
11661
  let push;
12076
11662
  let spsfound = false;
@@ -12086,49 +11672,42 @@ class HevcVideoParser extends BaseVideoParser {
12086
11672
  units.forEach(unit => {
12087
11673
  var _VideoSample2;
12088
11674
  switch (unit.type) {
12089
- // NON-IDR, NON RANDOM ACCESS SLICE
12090
- case 0:
11675
+ // NDR
12091
11676
  case 1:
12092
- case 2:
12093
- case 3:
12094
- case 4:
12095
- case 5:
12096
- case 6:
12097
- case 7:
12098
- case 8:
12099
- case 9:
12100
- if (!VideoSample) {
12101
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12102
- }
12103
- VideoSample.frame = true;
12104
- push = true;
12105
- break;
12106
-
12107
- // CRA, BLA (random access picture)
12108
- case 16:
12109
- case 17:
12110
- case 18:
12111
- case 21:
12112
- push = true;
12113
- if (spsfound) {
12114
- var _VideoSample;
12115
- // handle PES not starting with AUD
12116
- // if we have frame data already, that cannot belong to the same frame, so force a push
12117
- if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
12118
- this.pushAccessUnit(VideoSample, track);
12119
- VideoSample = this.VideoSample = null;
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
+ }
12120
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, '');
11704
+ }
11705
+ VideoSample.frame = true;
11706
+ VideoSample.key = iskey;
11707
+ break;
11708
+ // IDR
12121
11709
  }
12122
- if (!VideoSample) {
12123
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12124
- }
12125
- VideoSample.key = true;
12126
- VideoSample.frame = true;
12127
- break;
12128
-
12129
- // IDR
12130
- case 19:
12131
- case 20:
11710
+ case 5:
12132
11711
  push = true;
12133
11712
  // handle PES not starting with AUD
12134
11713
  // if we have frame data already, that cannot belong to the same frame, so force a push
@@ -12142,76 +11721,48 @@ class HevcVideoParser extends BaseVideoParser {
12142
11721
  VideoSample.key = true;
12143
11722
  VideoSample.frame = true;
12144
11723
  break;
12145
-
12146
11724
  // SEI
12147
- case 39:
12148
- push = true;
12149
- parseSEIMessageFromNALu(unit.data, 2,
12150
- // NALu header size
12151
- pes.pts, textTrack.samples);
12152
- break;
12153
-
12154
- // VPS
12155
- case 32:
12156
- push = true;
12157
- if (!track.vps) {
12158
- const config = this.readVPS(unit.data);
12159
- track.params = _objectSpread2({}, config);
12160
- this.initVPS = unit.data;
11725
+ case 6:
11726
+ {
11727
+ push = true;
11728
+ parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
11729
+ break;
11730
+ // SPS
12161
11731
  }
12162
- track.vps = [unit.data];
12163
- break;
12164
-
12165
- // SPS
12166
- case 33:
12167
- push = true;
12168
- spsfound = true;
12169
- if (typeof track.params === 'object') {
12170
- if (track.vps !== undefined && track.vps[0] !== this.initVPS && track.sps !== undefined && !this.matchSPS(track.sps[0], unit.data)) {
12171
- this.initVPS = track.vps[0];
12172
- track.sps = track.pps = undefined;
12173
- }
12174
- if (!track.sps) {
12175
- const config = this.readSPS(unit.data);
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]) {
12176
11741
  track.width = config.width;
12177
11742
  track.height = config.height;
12178
11743
  track.pixelRatio = config.pixelRatio;
11744
+ track.sps = [sps];
12179
11745
  track.duration = duration;
12180
- track.codec = config.codecString;
12181
- track.sps = [];
12182
- for (const prop in config.params) {
12183
- track.params[prop] = config.params[prop];
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;
12184
11754
  }
11755
+ track.codec = codecstring;
12185
11756
  }
12186
- if (track.vps !== undefined && track.vps[0] === this.initVPS) {
12187
- track.sps.push(unit.data);
12188
- }
12189
- }
12190
- if (!VideoSample) {
12191
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11757
+ break;
12192
11758
  }
12193
- VideoSample.key = true;
12194
- break;
12195
-
12196
11759
  // PPS
12197
- case 34:
11760
+ case 8:
12198
11761
  push = true;
12199
- if (typeof track.params === 'object') {
12200
- if (!track.pps) {
12201
- track.pps = [];
12202
- const config = this.readPPS(unit.data);
12203
- for (const prop in config) {
12204
- track.params[prop] = config[prop];
12205
- }
12206
- }
12207
- if (this.initVPS !== null || track.pps.length === 0) {
12208
- track.pps.push(unit.data);
12209
- }
12210
- }
11762
+ track.pps = [unit.data];
12211
11763
  break;
12212
-
12213
- // ACCESS UNIT DELIMITER
12214
- case 35:
11764
+ // AUD
11765
+ case 9:
12215
11766
  push = true;
12216
11767
  track.audFound = true;
12217
11768
  if (VideoSample) {
@@ -12219,10 +11770,14 @@ class HevcVideoParser extends BaseVideoParser {
12219
11770
  }
12220
11771
  VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12221
11772
  break;
11773
+ // Filler Data
11774
+ case 12:
11775
+ push = true;
11776
+ break;
12222
11777
  default:
12223
11778
  push = false;
12224
11779
  if (VideoSample) {
12225
- VideoSample.debug += 'unknown or irrelevant NAL ' + unit.type + ' ';
11780
+ VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
12226
11781
  }
12227
11782
  break;
12228
11783
  }
@@ -12237,423 +11792,109 @@ class HevcVideoParser extends BaseVideoParser {
12237
11792
  this.VideoSample = null;
12238
11793
  }
12239
11794
  }
12240
- getNALuType(data, offset) {
12241
- return (data[offset] & 0x7e) >>> 1;
12242
- }
12243
- ebsp2rbsp(arr) {
12244
- const dst = new Uint8Array(arr.byteLength);
12245
- let dstIdx = 0;
12246
- for (let i = 0; i < arr.byteLength; i++) {
12247
- if (i >= 2) {
12248
- // Unescape: Skip 0x03 after 00 00
12249
- if (arr[i] === 0x03 && arr[i - 1] === 0x00 && arr[i - 2] === 0x00) {
12250
- continue;
12251
- }
12252
- }
12253
- dst[dstIdx] = arr[i];
12254
- dstIdx++;
12255
- }
12256
- return new Uint8Array(dst.buffer, 0, dstIdx);
12257
- }
12258
- readVPS(vps) {
12259
- const eg = new ExpGolomb(vps);
12260
- // remove header
12261
- eg.readUByte();
12262
- eg.readUByte();
12263
- eg.readBits(4); // video_parameter_set_id
12264
- eg.skipBits(2);
12265
- eg.readBits(6); // max_layers_minus1
12266
- const max_sub_layers_minus1 = eg.readBits(3);
12267
- const temporal_id_nesting_flag = eg.readBoolean();
12268
- // ...vui fps can be here, but empty fps value is not critical for metadata
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));
12269
11807
 
12270
- return {
12271
- numTemporalLayers: max_sub_layers_minus1 + 1,
12272
- temporalIdNested: temporal_id_nesting_flag
12273
- };
12274
- }
12275
- readSPS(sps) {
12276
- const eg = new ExpGolomb(this.ebsp2rbsp(sps));
12277
- eg.readUByte();
12278
- eg.readUByte();
12279
- eg.readBits(4); //video_parameter_set_id
12280
- const max_sub_layers_minus1 = eg.readBits(3);
12281
- eg.readBoolean(); // temporal_id_nesting_flag
12282
-
12283
- // profile_tier_level
12284
- const general_profile_space = eg.readBits(2);
12285
- const general_tier_flag = eg.readBoolean();
12286
- const general_profile_idc = eg.readBits(5);
12287
- const general_profile_compatibility_flags_1 = eg.readUByte();
12288
- const general_profile_compatibility_flags_2 = eg.readUByte();
12289
- const general_profile_compatibility_flags_3 = eg.readUByte();
12290
- const general_profile_compatibility_flags_4 = eg.readUByte();
12291
- const general_constraint_indicator_flags_1 = eg.readUByte();
12292
- const general_constraint_indicator_flags_2 = eg.readUByte();
12293
- const general_constraint_indicator_flags_3 = eg.readUByte();
12294
- const general_constraint_indicator_flags_4 = eg.readUByte();
12295
- const general_constraint_indicator_flags_5 = eg.readUByte();
12296
- const general_constraint_indicator_flags_6 = eg.readUByte();
12297
- const general_level_idc = eg.readUByte();
12298
- const sub_layer_profile_present_flags = [];
12299
- const sub_layer_level_present_flags = [];
12300
- for (let i = 0; i < max_sub_layers_minus1; i++) {
12301
- sub_layer_profile_present_flags.push(eg.readBoolean());
12302
- sub_layer_level_present_flags.push(eg.readBoolean());
12303
- }
12304
- if (max_sub_layers_minus1 > 0) {
12305
- for (let i = max_sub_layers_minus1; i < 8; i++) {
12306
- eg.readBits(2);
12307
- }
12308
- }
12309
- for (let i = 0; i < max_sub_layers_minus1; i++) {
12310
- if (sub_layer_profile_present_flags[i]) {
12311
- eg.readUByte(); // sub_layer_profile_space, sub_layer_tier_flag, sub_layer_profile_idc
12312
- eg.readUByte();
12313
- eg.readUByte();
12314
- eg.readUByte();
12315
- eg.readUByte(); // sub_layer_profile_compatibility_flag
12316
- eg.readUByte();
12317
- eg.readUByte();
12318
- eg.readUByte();
12319
- eg.readUByte();
12320
- eg.readUByte();
12321
- eg.readUByte();
12322
- }
12323
- if (sub_layer_level_present_flags[i]) {
12324
- eg.readUByte();
12325
- }
12326
- }
12327
- eg.readUEG(); // seq_parameter_set_id
12328
- const chroma_format_idc = eg.readUEG();
12329
- if (chroma_format_idc == 3) {
12330
- eg.skipBits(1); //separate_colour_plane_flag
12331
- }
12332
- const pic_width_in_luma_samples = eg.readUEG();
12333
- const pic_height_in_luma_samples = eg.readUEG();
12334
- const conformance_window_flag = eg.readBoolean();
12335
- let pic_left_offset = 0,
12336
- pic_right_offset = 0,
12337
- pic_top_offset = 0,
12338
- pic_bottom_offset = 0;
12339
- if (conformance_window_flag) {
12340
- pic_left_offset += eg.readUEG();
12341
- pic_right_offset += eg.readUEG();
12342
- pic_top_offset += eg.readUEG();
12343
- pic_bottom_offset += eg.readUEG();
12344
- }
12345
- const bit_depth_luma_minus8 = eg.readUEG();
12346
- const bit_depth_chroma_minus8 = eg.readUEG();
12347
- const log2_max_pic_order_cnt_lsb_minus4 = eg.readUEG();
12348
- const sub_layer_ordering_info_present_flag = eg.readBoolean();
12349
- for (let i = sub_layer_ordering_info_present_flag ? 0 : max_sub_layers_minus1; i <= max_sub_layers_minus1; i++) {
12350
- eg.skipUEG(); // max_dec_pic_buffering_minus1[i]
12351
- eg.skipUEG(); // max_num_reorder_pics[i]
12352
- eg.skipUEG(); // max_latency_increase_plus1[i]
12353
- }
12354
- eg.skipUEG(); // log2_min_luma_coding_block_size_minus3
12355
- eg.skipUEG(); // log2_diff_max_min_luma_coding_block_size
12356
- eg.skipUEG(); // log2_min_transform_block_size_minus2
12357
- eg.skipUEG(); // log2_diff_max_min_transform_block_size
12358
- eg.skipUEG(); // max_transform_hierarchy_depth_inter
12359
- eg.skipUEG(); // max_transform_hierarchy_depth_intra
12360
- const scaling_list_enabled_flag = eg.readBoolean();
12361
- if (scaling_list_enabled_flag) {
12362
- const sps_scaling_list_data_present_flag = eg.readBoolean();
12363
- if (sps_scaling_list_data_present_flag) {
12364
- for (let sizeId = 0; sizeId < 4; sizeId++) {
12365
- for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
12366
- const scaling_list_pred_mode_flag = eg.readBoolean();
12367
- if (!scaling_list_pred_mode_flag) {
12368
- eg.readUEG(); // scaling_list_pred_matrix_id_delta
12369
- } else {
12370
- const coefNum = Math.min(64, 1 << 4 + (sizeId << 1));
12371
- if (sizeId > 1) {
12372
- eg.readEG();
12373
- }
12374
- for (let i = 0; i < coefNum; i++) {
12375
- eg.readEG();
12376
- }
12377
- }
12378
- }
12379
- }
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;
12380
11826
  }
12381
- }
12382
- eg.readBoolean(); // amp_enabled_flag
12383
- eg.readBoolean(); // sample_adaptive_offset_enabled_flag
12384
- const pcm_enabled_flag = eg.readBoolean();
12385
- if (pcm_enabled_flag) {
12386
- eg.readUByte();
12387
- eg.skipUEG();
12388
- eg.skipUEG();
12389
- eg.readBoolean();
12390
- }
12391
- const num_short_term_ref_pic_sets = eg.readUEG();
12392
- let num_delta_pocs = 0;
12393
- for (let i = 0; i < num_short_term_ref_pic_sets; i++) {
12394
- let inter_ref_pic_set_prediction_flag = false;
12395
- if (i !== 0) {
12396
- inter_ref_pic_set_prediction_flag = eg.readBoolean();
12397
- }
12398
- if (inter_ref_pic_set_prediction_flag) {
12399
- if (i === num_short_term_ref_pic_sets) {
12400
- eg.readUEG();
12401
- }
12402
- eg.readBoolean();
12403
- eg.readUEG();
12404
- let next_num_delta_pocs = 0;
12405
- for (let j = 0; j <= num_delta_pocs; j++) {
12406
- const used_by_curr_pic_flag = eg.readBoolean();
12407
- let use_delta_flag = false;
12408
- if (!used_by_curr_pic_flag) {
12409
- use_delta_flag = eg.readBoolean();
12410
- }
12411
- if (used_by_curr_pic_flag || use_delta_flag) {
12412
- next_num_delta_pocs++;
12413
- }
12414
- }
12415
- num_delta_pocs = next_num_delta_pocs;
12416
- } else {
12417
- const num_negative_pics = eg.readUEG();
12418
- const num_positive_pics = eg.readUEG();
12419
- num_delta_pocs = num_negative_pics + num_positive_pics;
12420
- for (let j = 0; j < num_negative_pics; j++) {
12421
- eg.readUEG();
12422
- eg.readBoolean();
12423
- }
12424
- for (let j = 0; j < num_positive_pics; j++) {
12425
- eg.readUEG();
12426
- eg.readBoolean();
12427
- }
12428
- }
12429
- }
12430
- const long_term_ref_pics_present_flag = eg.readBoolean();
12431
- if (long_term_ref_pics_present_flag) {
12432
- const num_long_term_ref_pics_sps = eg.readUEG();
12433
- for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
12434
- for (let j = 0; j < log2_max_pic_order_cnt_lsb_minus4 + 4; j++) {
12435
- eg.readBits(1);
12436
- }
12437
- eg.readBits(1);
12438
- }
12439
- }
12440
- let min_spatial_segmentation_idc = 0;
12441
- let sar_width = 1,
12442
- sar_height = 1;
12443
- let fps_fixed = true,
12444
- fps_den = 1,
12445
- fps_num = 0;
12446
- eg.readBoolean(); // sps_temporal_mvp_enabled_flag
12447
- eg.readBoolean(); // strong_intra_smoothing_enabled_flag
12448
- let default_display_window_flag = false;
12449
- const vui_parameters_present_flag = eg.readBoolean();
12450
- if (vui_parameters_present_flag) {
12451
- const aspect_ratio_info_present_flag = eg.readBoolean();
12452
- if (aspect_ratio_info_present_flag) {
12453
- const aspect_ratio_idc = eg.readUByte();
12454
- const sar_width_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];
12455
- const sar_height_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1];
12456
- if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) {
12457
- sar_width = sar_width_table[aspect_ratio_idc - 1];
12458
- sar_height = sar_height_table[aspect_ratio_idc - 1];
12459
- } else if (aspect_ratio_idc === 255) {
12460
- sar_width = eg.readBits(16);
12461
- sar_height = eg.readBits(16);
12462
- }
12463
- }
12464
- const overscan_info_present_flag = eg.readBoolean();
12465
- if (overscan_info_present_flag) {
12466
- eg.readBoolean();
12467
- }
12468
- const video_signal_type_present_flag = eg.readBoolean();
12469
- if (video_signal_type_present_flag) {
12470
- eg.readBits(3);
12471
- eg.readBoolean();
12472
- const colour_description_present_flag = eg.readBoolean();
12473
- if (colour_description_present_flag) {
12474
- eg.readUByte();
12475
- eg.readUByte();
12476
- eg.readUByte();
12477
- }
12478
- }
12479
- const chroma_loc_info_present_flag = eg.readBoolean();
12480
- if (chroma_loc_info_present_flag) {
12481
- eg.readUEG();
12482
- eg.readUEG();
12483
- }
12484
- eg.readBoolean(); // neutral_chroma_indication_flag
12485
- eg.readBoolean(); // field_seq_flag
12486
- eg.readBoolean(); // frame_field_info_present_flag
12487
- default_display_window_flag = eg.readBoolean();
12488
- if (default_display_window_flag) {
12489
- pic_left_offset += eg.readUEG();
12490
- pic_right_offset += eg.readUEG();
12491
- pic_top_offset += eg.readUEG();
12492
- pic_bottom_offset += eg.readUEG();
12493
- }
12494
- const vui_timing_info_present_flag = eg.readBoolean();
12495
- if (vui_timing_info_present_flag) {
12496
- fps_den = eg.readBits(32);
12497
- fps_num = eg.readBits(32);
12498
- const vui_poc_proportional_to_timing_flag = eg.readBoolean();
12499
- if (vui_poc_proportional_to_timing_flag) {
12500
- eg.readUEG();
12501
- }
12502
- const vui_hrd_parameters_present_flag = eg.readBoolean();
12503
- if (vui_hrd_parameters_present_flag) {
12504
- //const commonInfPresentFlag = true;
12505
- //if (commonInfPresentFlag) {
12506
- const nal_hrd_parameters_present_flag = eg.readBoolean();
12507
- const vcl_hrd_parameters_present_flag = eg.readBoolean();
12508
- let sub_pic_hrd_params_present_flag = false;
12509
- if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
12510
- sub_pic_hrd_params_present_flag = eg.readBoolean();
12511
- if (sub_pic_hrd_params_present_flag) {
12512
- eg.readUByte();
12513
- eg.readBits(5);
12514
- eg.readBoolean();
12515
- eg.readBits(5);
12516
- }
12517
- eg.readBits(4); // bit_rate_scale
12518
- eg.readBits(4); // cpb_size_scale
12519
- if (sub_pic_hrd_params_present_flag) {
12520
- eg.readBits(4);
12521
- }
12522
- eg.readBits(5);
12523
- eg.readBits(5);
12524
- eg.readBits(5);
12525
- }
12526
- //}
12527
- for (let i = 0; i <= max_sub_layers_minus1; i++) {
12528
- fps_fixed = eg.readBoolean(); // fixed_pic_rate_general_flag
12529
- const fixed_pic_rate_within_cvs_flag = fps_fixed || eg.readBoolean();
12530
- let low_delay_hrd_flag = false;
12531
- if (fixed_pic_rate_within_cvs_flag) {
12532
- eg.readEG();
12533
- } else {
12534
- low_delay_hrd_flag = eg.readBoolean();
12535
- }
12536
- const cpb_cnt = low_delay_hrd_flag ? 1 : eg.readUEG() + 1;
12537
- if (nal_hrd_parameters_present_flag) {
12538
- for (let j = 0; j < cpb_cnt; j++) {
12539
- eg.readUEG();
12540
- eg.readUEG();
12541
- if (sub_pic_hrd_params_present_flag) {
12542
- eg.readUEG();
12543
- eg.readUEG();
12544
- }
12545
- eg.skipBits(1);
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);
12546
11853
  }
12547
11854
  }
12548
- if (vcl_hrd_parameters_present_flag) {
12549
- for (let j = 0; j < cpb_cnt; j++) {
12550
- eg.readUEG();
12551
- eg.readUEG();
12552
- if (sub_pic_hrd_params_present_flag) {
12553
- eg.readUEG();
12554
- eg.readUEG();
12555
- }
12556
- eg.skipBits(1);
12557
- }
11855
+ // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
11856
+
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;
12558
11861
  }
12559
11862
  }
12560
11863
  }
12561
- }
12562
- const bitstream_restriction_flag = eg.readBoolean();
12563
- if (bitstream_restriction_flag) {
12564
- eg.readBoolean(); // tiles_fixed_structure_flag
12565
- eg.readBoolean(); // motion_vectors_over_pic_boundaries_flag
12566
- eg.readBoolean(); // restricted_ref_pic_lists_flag
12567
- min_spatial_segmentation_idc = eg.readUEG();
12568
- }
12569
- }
12570
- let width = pic_width_in_luma_samples,
12571
- height = pic_height_in_luma_samples;
12572
- if (conformance_window_flag || default_display_window_flag) {
12573
- let chroma_scale_w = 1,
12574
- chroma_scale_h = 1;
12575
- if (chroma_format_idc === 1) {
12576
- // YUV 420
12577
- chroma_scale_w = chroma_scale_h = 2;
12578
- } else if (chroma_format_idc == 2) {
12579
- // YUV 422
12580
- chroma_scale_w = 2;
12581
- }
12582
- width = pic_width_in_luma_samples - chroma_scale_w * pic_right_offset - chroma_scale_w * pic_left_offset;
12583
- height = pic_height_in_luma_samples - chroma_scale_h * pic_bottom_offset - chroma_scale_h * pic_top_offset;
12584
- }
12585
- const profile_space_string = general_profile_space ? ['A', 'B', 'C'][general_profile_space] : '';
12586
- 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;
12587
- let profile_compatibility_rev = 0;
12588
- for (let i = 0; i < 32; i++) {
12589
- profile_compatibility_rev = (profile_compatibility_rev | (profile_compatibility_buf >> i & 1) << 31 - i) >>> 0; // reverse bit position (and cast as UInt32)
12590
- }
12591
- let profile_compatibility_flags_string = profile_compatibility_rev.toString(16);
12592
- if (general_profile_idc === 1 && profile_compatibility_flags_string === '2') {
12593
- profile_compatibility_flags_string = '6';
12594
- }
12595
- const tier_flag_string = general_tier_flag ? 'H' : 'L';
12596
- return {
12597
- codecString: `hvc1.${profile_space_string}${general_profile_idc}.${profile_compatibility_flags_string}.${tier_flag_string}${general_level_idc}.B0`,
12598
- params: {
12599
- general_tier_flag,
12600
- general_profile_idc,
12601
- general_profile_space,
12602
- general_profile_compatibility_flags: [general_profile_compatibility_flags_1, general_profile_compatibility_flags_2, general_profile_compatibility_flags_3, general_profile_compatibility_flags_4],
12603
- 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],
12604
- general_level_idc,
12605
- bit_depth: bit_depth_luma_minus8 + 8,
12606
- bit_depth_luma_minus8,
12607
- bit_depth_chroma_minus8,
12608
- min_spatial_segmentation_idc,
12609
- chroma_format_idc: chroma_format_idc,
12610
- frame_rate: {
12611
- fixed: fps_fixed,
12612
- fps: fps_num / fps_den
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;
12613
11874
  }
12614
- },
12615
- width,
12616
- height,
12617
- pixelRatio: [sar_width, sar_height]
12618
- };
12619
- }
12620
- readPPS(pps) {
12621
- const eg = new ExpGolomb(this.ebsp2rbsp(pps));
12622
- eg.readUByte();
12623
- eg.readUByte();
12624
- eg.skipUEG(); // pic_parameter_set_id
12625
- eg.skipUEG(); // seq_parameter_set_id
12626
- eg.skipBits(2); // dependent_slice_segments_enabled_flag, output_flag_present_flag
12627
- eg.skipBits(3); // num_extra_slice_header_bits
12628
- eg.skipBits(2); // sign_data_hiding_enabled_flag, cabac_init_present_flag
12629
- eg.skipUEG();
12630
- eg.skipUEG();
12631
- eg.skipEG(); // init_qp_minus26
12632
- eg.skipBits(2); // constrained_intra_pred_flag, transform_skip_enabled_flag
12633
- const cu_qp_delta_enabled_flag = eg.readBoolean();
12634
- if (cu_qp_delta_enabled_flag) {
12635
- eg.skipUEG();
12636
- }
12637
- eg.skipEG(); // cb_qp_offset
12638
- eg.skipEG(); // cr_qp_offset
12639
- eg.skipBits(4); // pps_slice_chroma_qp_offsets_present_flag, weighted_pred_flag, weighted_bipred_flag, transquant_bypass_enabled_flag
12640
- const tiles_enabled_flag = eg.readBoolean();
12641
- const entropy_coding_sync_enabled_flag = eg.readBoolean();
12642
- let parallelismType = 1; // slice-based parallel decoding
12643
- if (entropy_coding_sync_enabled_flag && tiles_enabled_flag) {
12644
- parallelismType = 0; // mixed-type parallel decoding
12645
- } else if (entropy_coding_sync_enabled_flag) {
12646
- parallelismType = 3; // wavefront-based parallel decoding
12647
- } else if (tiles_enabled_flag) {
12648
- parallelismType = 2; // tile-based parallel decoding
11875
+ } else {
11876
+ state = 0;
11877
+ }
12649
11878
  }
12650
- return {
12651
- parallelismType
12652
- };
12653
- }
12654
- matchSPS(sps1, sps2) {
12655
- // compare without headers and VPS related params
12656
- return String.fromCharCode.apply(null, sps1).substr(3) === String.fromCharCode.apply(null, sps2).substr(3);
11879
+ if (lastUnitStart >= 0 && state >= 0) {
11880
+ const unit = {
11881
+ data: array.subarray(lastUnitStart, len),
11882
+ type: lastUnitType,
11883
+ state: state
11884
+ };
11885
+ units.push(unit);
11886
+ // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
11887
+ }
11888
+ // no NALu found
11889
+ if (units.length === 0) {
11890
+ // append pes.data to previous NAL unit
11891
+ const lastUnit = this.getLastNalUnit(track.samples);
11892
+ if (lastUnit) {
11893
+ lastUnit.data = appendUint8Array(lastUnit.data, array);
11894
+ }
11895
+ }
11896
+ track.naluState = state;
11897
+ return units;
12657
11898
  }
12658
11899
  }
12659
11900
 
@@ -12671,7 +11912,7 @@ class SampleAesDecrypter {
12671
11912
  });
12672
11913
  }
12673
11914
  decryptBuffer(encryptedData) {
12674
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
11915
+ return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
12675
11916
  }
12676
11917
 
12677
11918
  // AAC - encrypt all full 16 bytes blocks starting from offset 16
@@ -12785,7 +12026,7 @@ class TSDemuxer {
12785
12026
  this.observer = observer;
12786
12027
  this.config = config;
12787
12028
  this.typeSupported = typeSupported;
12788
- this.videoParser = null;
12029
+ this.videoParser = new AvcVideoParser();
12789
12030
  }
12790
12031
  static probe(data) {
12791
12032
  const syncOffset = TSDemuxer.syncOffset(data);
@@ -12950,21 +12191,7 @@ class TSDemuxer {
12950
12191
  case videoPid:
12951
12192
  if (stt) {
12952
12193
  if (videoData && (pes = parsePES(videoData))) {
12953
- if (this.videoParser === null) {
12954
- switch (videoTrack.segmentCodec) {
12955
- case 'avc':
12956
- this.videoParser = new AvcVideoParser();
12957
- break;
12958
- case 'hevc':
12959
- {
12960
- this.videoParser = new HevcVideoParser();
12961
- }
12962
- break;
12963
- }
12964
- }
12965
- if (this.videoParser !== null) {
12966
- this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
12967
- }
12194
+ this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
12968
12195
  }
12969
12196
  videoData = {
12970
12197
  data: [],
@@ -13131,22 +12358,8 @@ class TSDemuxer {
13131
12358
  // try to parse last PES packets
13132
12359
  let pes;
13133
12360
  if (videoData && (pes = parsePES(videoData))) {
13134
- if (this.videoParser === null) {
13135
- switch (videoTrack.segmentCodec) {
13136
- case 'avc':
13137
- this.videoParser = new AvcVideoParser();
13138
- break;
13139
- case 'hevc':
13140
- {
13141
- this.videoParser = new HevcVideoParser();
13142
- }
13143
- break;
13144
- }
13145
- }
13146
- if (this.videoParser !== null) {
13147
- this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
13148
- videoTrack.pesData = null;
13149
- }
12361
+ this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
12362
+ videoTrack.pesData = null;
13150
12363
  } else {
13151
12364
  // either avcData null or PES truncated, keep it for next frag parsing
13152
12365
  videoTrack.pesData = videoData;
@@ -13479,14 +12692,7 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
13479
12692
  logger.warn('Unsupported EC-3 in M2TS found');
13480
12693
  break;
13481
12694
  case 0x24:
13482
- // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
13483
- {
13484
- if (result.videoPid === -1) {
13485
- result.videoPid = pid;
13486
- result.segmentVideoCodec = 'hevc';
13487
- logger.log('HEVC in M2TS found');
13488
- }
13489
- }
12695
+ logger.warn('Unsupported HEVC in M2TS found');
13490
12696
  break;
13491
12697
  }
13492
12698
  // move to the next table entry
@@ -13709,8 +12915,6 @@ class MP4 {
13709
12915
  avc1: [],
13710
12916
  // codingname
13711
12917
  avcC: [],
13712
- hvc1: [],
13713
- hvcC: [],
13714
12918
  btrt: [],
13715
12919
  dinf: [],
13716
12920
  dref: [],
@@ -14135,10 +13339,8 @@ class MP4 {
14135
13339
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
14136
13340
  }
14137
13341
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
14138
- } else if (track.segmentCodec === 'avc') {
14139
- return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
14140
13342
  } else {
14141
- return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
13343
+ return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
14142
13344
  }
14143
13345
  }
14144
13346
  static tkhd(track) {
@@ -14276,84 +13478,6 @@ class MP4 {
14276
13478
  const result = appendUint8Array(MP4.FTYP, movie);
14277
13479
  return result;
14278
13480
  }
14279
- static hvc1(track) {
14280
- const ps = track.params;
14281
- const units = [track.vps, track.sps, track.pps];
14282
- const NALuLengthSize = 4;
14283
- 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]);
14284
-
14285
- // compute hvcC size in bytes
14286
- let length = config.length;
14287
- for (let i = 0; i < units.length; i += 1) {
14288
- length += 3;
14289
- for (let j = 0; j < units[i].length; j += 1) {
14290
- length += 2 + units[i][j].length;
14291
- }
14292
- }
14293
- const hvcC = new Uint8Array(length);
14294
- hvcC.set(config, 0);
14295
- length = config.length;
14296
- // append parameter set units: one vps, one or more sps and pps
14297
- const iMax = units.length - 1;
14298
- for (let i = 0; i < units.length; i += 1) {
14299
- hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
14300
- length += 3;
14301
- for (let j = 0; j < units[i].length; j += 1) {
14302
- hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
14303
- length += 2;
14304
- hvcC.set(units[i][j], length);
14305
- length += units[i][j].length;
14306
- }
14307
- }
14308
- const hvcc = MP4.box(MP4.types.hvcC, hvcC);
14309
- const width = track.width;
14310
- const height = track.height;
14311
- const hSpacing = track.pixelRatio[0];
14312
- const vSpacing = track.pixelRatio[1];
14313
- return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
14314
- // reserved
14315
- 0x00, 0x00, 0x00,
14316
- // reserved
14317
- 0x00, 0x01,
14318
- // data_reference_index
14319
- 0x00, 0x00,
14320
- // pre_defined
14321
- 0x00, 0x00,
14322
- // reserved
14323
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14324
- // pre_defined
14325
- width >> 8 & 0xff, width & 0xff,
14326
- // width
14327
- height >> 8 & 0xff, height & 0xff,
14328
- // height
14329
- 0x00, 0x48, 0x00, 0x00,
14330
- // horizresolution
14331
- 0x00, 0x48, 0x00, 0x00,
14332
- // vertresolution
14333
- 0x00, 0x00, 0x00, 0x00,
14334
- // reserved
14335
- 0x00, 0x01,
14336
- // frame_count
14337
- 0x12, 0x64, 0x61, 0x69, 0x6c,
14338
- // dailymotion/hls.js
14339
- 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,
14340
- // compressorname
14341
- 0x00, 0x18,
14342
- // depth = 24
14343
- 0x11, 0x11]),
14344
- // pre_defined = -1
14345
- hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
14346
- // bufferSizeDB
14347
- 0x00, 0x2d, 0xc6, 0xc0,
14348
- // maxBitrate
14349
- 0x00, 0x2d, 0xc6, 0xc0])),
14350
- // avgBitrate
14351
- MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
14352
- // hSpacing
14353
- hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
14354
- // vSpacing
14355
- vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
14356
- }
14357
13481
  }
14358
13482
  MP4.types = void 0;
14359
13483
  MP4.HDLR_TYPES = void 0;
@@ -14735,9 +13859,9 @@ class MP4Remuxer {
14735
13859
  const foundOverlap = delta < -1;
14736
13860
  if (foundHole || foundOverlap) {
14737
13861
  if (foundHole) {
14738
- logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
13862
+ logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
14739
13863
  } else {
14740
- logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
13864
+ logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
14741
13865
  }
14742
13866
  if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
14743
13867
  firstDTS = nextAvcDts;
@@ -14746,24 +13870,12 @@ class MP4Remuxer {
14746
13870
  inputSamples[0].dts = firstDTS;
14747
13871
  inputSamples[0].pts = firstPTS;
14748
13872
  } else {
14749
- let isPTSOrderRetained = true;
14750
13873
  for (let i = 0; i < inputSamples.length; i++) {
14751
- if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
13874
+ if (inputSamples[i].dts > firstPTS) {
14752
13875
  break;
14753
13876
  }
14754
- const prevPTS = inputSamples[i].pts;
14755
13877
  inputSamples[i].dts -= delta;
14756
13878
  inputSamples[i].pts -= delta;
14757
-
14758
- // check to see if this sample's PTS order has changed
14759
- // relative to the next one
14760
- if (i < inputSamples.length - 1) {
14761
- const nextSamplePTS = inputSamples[i + 1].pts;
14762
- const currentSamplePTS = inputSamples[i].pts;
14763
- const currentOrder = nextSamplePTS <= currentSamplePTS;
14764
- const prevOrder = nextSamplePTS <= prevPTS;
14765
- isPTSOrderRetained = currentOrder == prevOrder;
14766
- }
14767
13879
  }
14768
13880
  }
14769
13881
  logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
@@ -14911,7 +14023,7 @@ class MP4Remuxer {
14911
14023
  }
14912
14024
  }
14913
14025
  }
14914
- // next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14026
+ // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14915
14027
  mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
14916
14028
  this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
14917
14029
  this.videoSampleDuration = mp4SampleDuration;
@@ -15044,7 +14156,7 @@ class MP4Remuxer {
15044
14156
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
15045
14157
  for (let j = 0; j < missing; j++) {
15046
14158
  const newStamp = Math.max(nextPts, 0);
15047
- let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14159
+ let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
15048
14160
  if (!fillFrame) {
15049
14161
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
15050
14162
  fillFrame = sample.unit.subarray();
@@ -15172,7 +14284,7 @@ class MP4Remuxer {
15172
14284
  // samples count of this segment's duration
15173
14285
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
15174
14286
  // silent frame
15175
- const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14287
+ const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
15176
14288
  logger.warn('[mp4-remuxer]: remux empty Audio');
15177
14289
  // Can't remux if we can't generate a silent frame...
15178
14290
  if (!silentFrame) {
@@ -15566,15 +14678,13 @@ class Transmuxer {
15566
14678
  initSegmentData
15567
14679
  } = transmuxConfig;
15568
14680
  const keyData = getEncryptionType(uintData, decryptdata);
15569
- if (keyData && isFullSegmentEncryption(keyData.method)) {
14681
+ if (keyData && keyData.method === 'AES-128') {
15570
14682
  const decrypter = this.getDecrypter();
15571
- const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
15572
-
15573
14683
  // Software decryption is synchronous; webCrypto is not
15574
14684
  if (decrypter.isSync()) {
15575
14685
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
15576
14686
  // data is handled in the flush() call
15577
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
14687
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
15578
14688
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
15579
14689
  const loadingParts = chunkMeta.part > -1;
15580
14690
  if (loadingParts) {
@@ -15586,7 +14696,7 @@ class Transmuxer {
15586
14696
  }
15587
14697
  uintData = new Uint8Array(decryptedData);
15588
14698
  } else {
15589
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
14699
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
15590
14700
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
15591
14701
  // the decrypted data has been transmuxed
15592
14702
  const result = this.push(decryptedData, null, chunkMeta);
@@ -16240,7 +15350,14 @@ class TransmuxerInterface {
16240
15350
  this.observer = new EventEmitter();
16241
15351
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
16242
15352
  this.observer.on(Events.ERROR, forwardMessage);
16243
- const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
15353
+ const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
15354
+ isTypeSupported: () => false
15355
+ };
15356
+ const m2tsTypeSupported = {
15357
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
15358
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
15359
+ ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
15360
+ };
16244
15361
 
16245
15362
  // navigator.vendor is not always available in Web Worker
16246
15363
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -16528,7 +15645,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
16528
15645
 
16529
15646
  class AudioStreamController extends BaseStreamController {
16530
15647
  constructor(hls, fragmentTracker, keyLoader) {
16531
- super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
15648
+ super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);
16532
15649
  this.videoBuffer = null;
16533
15650
  this.videoTrackCC = -1;
16534
15651
  this.waitingVideoCC = -1;
@@ -16540,24 +15657,27 @@ class AudioStreamController extends BaseStreamController {
16540
15657
  this.flushing = false;
16541
15658
  this.bufferFlushed = false;
16542
15659
  this.cachedTrackLoadedData = null;
16543
- this.registerListeners();
15660
+ this._registerListeners();
16544
15661
  }
16545
15662
  onHandlerDestroying() {
16546
- this.unregisterListeners();
15663
+ this._unregisterListeners();
16547
15664
  super.onHandlerDestroying();
16548
15665
  this.mainDetails = null;
16549
15666
  this.bufferedTrack = null;
16550
15667
  this.switchingTrack = null;
16551
15668
  }
16552
- registerListeners() {
16553
- super.registerListeners();
15669
+ _registerListeners() {
16554
15670
  const {
16555
15671
  hls
16556
15672
  } = 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);
16557
15676
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16558
15677
  hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
16559
15678
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
16560
15679
  hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15680
+ hls.on(Events.ERROR, this.onError, this);
16561
15681
  hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
16562
15682
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
16563
15683
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -16565,18 +15685,18 @@ class AudioStreamController extends BaseStreamController {
16565
15685
  hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
16566
15686
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
16567
15687
  }
16568
- unregisterListeners() {
15688
+ _unregisterListeners() {
16569
15689
  const {
16570
15690
  hls
16571
15691
  } = this;
16572
- if (!hls) {
16573
- return;
16574
- }
16575
- super.unregisterListeners();
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);
16576
15695
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16577
15696
  hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
16578
15697
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
16579
15698
  hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15699
+ hls.off(Events.ERROR, this.onError, this);
16580
15700
  hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
16581
15701
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
16582
15702
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -16745,13 +15865,12 @@ class AudioStreamController extends BaseStreamController {
16745
15865
  } = this;
16746
15866
  const config = hls.config;
16747
15867
 
16748
- // 1. if buffering is suspended
16749
- // 2. if video not attached AND
15868
+ // 1. if video not attached AND
16750
15869
  // start fragment already requested OR start frag prefetch not enabled
16751
- // 3. if tracks or track not loaded and selected
15870
+ // 2. if tracks or track not loaded and selected
16752
15871
  // then exit loop
16753
15872
  // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
16754
- if (!this.buffering || !media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
15873
+ if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
16755
15874
  return;
16756
15875
  }
16757
15876
  const levelInfo = levels[trackId];
@@ -17309,7 +16428,7 @@ class AudioStreamController extends BaseStreamController {
17309
16428
 
17310
16429
  class AudioTrackController extends BasePlaylistController {
17311
16430
  constructor(hls) {
17312
- super(hls, 'audio-track-controller');
16431
+ super(hls, '[audio-track-controller]');
17313
16432
  this.tracks = [];
17314
16433
  this.groupIds = null;
17315
16434
  this.tracksInGroup = [];
@@ -17628,23 +16747,26 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
17628
16747
 
17629
16748
  class SubtitleStreamController extends BaseStreamController {
17630
16749
  constructor(hls, fragmentTracker, keyLoader) {
17631
- super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
16750
+ super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);
17632
16751
  this.currentTrackId = -1;
17633
16752
  this.tracksBuffered = [];
17634
16753
  this.mainDetails = null;
17635
- this.registerListeners();
16754
+ this._registerListeners();
17636
16755
  }
17637
16756
  onHandlerDestroying() {
17638
- this.unregisterListeners();
16757
+ this._unregisterListeners();
17639
16758
  super.onHandlerDestroying();
17640
16759
  this.mainDetails = null;
17641
16760
  }
17642
- registerListeners() {
17643
- super.registerListeners();
16761
+ _registerListeners() {
17644
16762
  const {
17645
16763
  hls
17646
16764
  } = 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);
17647
16768
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16769
+ hls.on(Events.ERROR, this.onError, this);
17648
16770
  hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
17649
16771
  hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
17650
16772
  hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -17652,12 +16774,15 @@ class SubtitleStreamController extends BaseStreamController {
17652
16774
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
17653
16775
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
17654
16776
  }
17655
- unregisterListeners() {
17656
- super.unregisterListeners();
16777
+ _unregisterListeners() {
17657
16778
  const {
17658
16779
  hls
17659
16780
  } = 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);
17660
16784
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16785
+ hls.off(Events.ERROR, this.onError, this);
17661
16786
  hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
17662
16787
  hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
17663
16788
  hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -17884,10 +17009,10 @@ class SubtitleStreamController extends BaseStreamController {
17884
17009
  return;
17885
17010
  }
17886
17011
  // check to see if the payload needs to be decrypted
17887
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
17012
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
17888
17013
  const startTime = performance.now();
17889
17014
  // decrypt the subtitles
17890
- this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
17015
+ this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
17891
17016
  hls.trigger(Events.ERROR, {
17892
17017
  type: ErrorTypes.MEDIA_ERROR,
17893
17018
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -18021,7 +17146,7 @@ class BufferableInstance {
18021
17146
 
18022
17147
  class SubtitleTrackController extends BasePlaylistController {
18023
17148
  constructor(hls) {
18024
- super(hls, 'subtitle-track-controller');
17149
+ super(hls, '[subtitle-track-controller]');
18025
17150
  this.media = null;
18026
17151
  this.tracks = [];
18027
17152
  this.groupIds = null;
@@ -18030,10 +17155,10 @@ class SubtitleTrackController extends BasePlaylistController {
18030
17155
  this.currentTrack = null;
18031
17156
  this.selectDefaultTrack = true;
18032
17157
  this.queuedDefaultTrack = -1;
17158
+ this.asyncPollTrackChange = () => this.pollTrackChange(0);
18033
17159
  this.useTextTrackPolling = false;
18034
17160
  this.subtitlePollingInterval = -1;
18035
17161
  this._subtitleDisplay = true;
18036
- this.asyncPollTrackChange = () => this.pollTrackChange(0);
18037
17162
  this.onTextTracksChanged = () => {
18038
17163
  if (!this.useTextTrackPolling) {
18039
17164
  self.clearInterval(this.subtitlePollingInterval);
@@ -18067,7 +17192,6 @@ class SubtitleTrackController extends BasePlaylistController {
18067
17192
  this.tracks.length = 0;
18068
17193
  this.tracksInGroup.length = 0;
18069
17194
  this.currentTrack = null;
18070
- // @ts-ignore
18071
17195
  this.onTextTracksChanged = this.asyncPollTrackChange = null;
18072
17196
  super.destroy();
18073
17197
  }
@@ -18528,9 +17652,8 @@ class BufferOperationQueue {
18528
17652
  }
18529
17653
 
18530
17654
  const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
18531
- class BufferController extends Logger {
17655
+ class BufferController {
18532
17656
  constructor(hls) {
18533
- super('buffer-controller', hls.logger);
18534
17657
  // The level details used to determine duration, target-duration and live
18535
17658
  this.details = null;
18536
17659
  // cache the self generated object url to detect hijack of video tag
@@ -18560,6 +17683,9 @@ class BufferController extends Logger {
18560
17683
  this.tracks = {};
18561
17684
  this.pendingTracks = {};
18562
17685
  this.sourceBuffer = void 0;
17686
+ this.log = void 0;
17687
+ this.warn = void 0;
17688
+ this.error = void 0;
18563
17689
  this._onEndStreaming = event => {
18564
17690
  if (!this.hls) {
18565
17691
  return;
@@ -18605,11 +17731,15 @@ class BufferController extends Logger {
18605
17731
  _objectUrl
18606
17732
  } = this;
18607
17733
  if (mediaSrc !== _objectUrl) {
18608
- this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
17734
+ logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
18609
17735
  }
18610
17736
  };
18611
17737
  this.hls = hls;
17738
+ const logPrefix = '[buffer-controller]';
18612
17739
  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);
18613
17743
  this._initSourceBuffer();
18614
17744
  this.registerListeners();
18615
17745
  }
@@ -18622,12 +17752,6 @@ class BufferController extends Logger {
18622
17752
  this.lastMpegAudioChunk = null;
18623
17753
  // @ts-ignore
18624
17754
  this.hls = null;
18625
- // @ts-ignore
18626
- this._onMediaSourceOpen = this._onMediaSourceClose = null;
18627
- // @ts-ignore
18628
- this._onMediaSourceEnded = null;
18629
- // @ts-ignore
18630
- this._onStartStreaming = this._onEndStreaming = null;
18631
17755
  }
18632
17756
  registerListeners() {
18633
17757
  const {
@@ -18790,7 +17914,6 @@ class BufferController extends Logger {
18790
17914
  this.resetBuffer(type);
18791
17915
  });
18792
17916
  this._initSourceBuffer();
18793
- this.hls.resumeBuffering();
18794
17917
  }
18795
17918
  resetBuffer(type) {
18796
17919
  const sb = this.sourceBuffer[type];
@@ -21893,12 +21016,14 @@ class TimelineController {
21893
21016
  this.cea608Parser1 = this.cea608Parser2 = undefined;
21894
21017
  }
21895
21018
  initCea608Parsers() {
21896
- const channel1 = new OutputFilter(this, 'textTrack1');
21897
- const channel2 = new OutputFilter(this, 'textTrack2');
21898
- const channel3 = new OutputFilter(this, 'textTrack3');
21899
- const channel4 = new OutputFilter(this, 'textTrack4');
21900
- this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
21901
- this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
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
+ }
21902
21027
  }
21903
21028
  addCues(trackName, startTime, endTime, screen, cueRanges) {
21904
21029
  // skip cues which overlap more than 50% with previously parsed time ranges
@@ -22136,7 +21261,7 @@ class TimelineController {
22136
21261
  if (inUseTracks != null && inUseTracks.length) {
22137
21262
  const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
22138
21263
  if (unusedTextTracks.length) {
22139
- this.hls.logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
21264
+ logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
22140
21265
  }
22141
21266
  }
22142
21267
  } else if (this.tracks.length) {
@@ -22181,23 +21306,26 @@ class TimelineController {
22181
21306
  return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
22182
21307
  }
22183
21308
  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
+ }
22184
21320
  // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
22185
- if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
21321
+ if (data.frag.type === PlaylistLevelType.MAIN) {
22186
21322
  var _data$part$index, _data$part;
22187
- const {
22188
- cea608Parser1,
22189
- cea608Parser2,
22190
- lastSn
22191
- } = this;
22192
- if (!cea608Parser1 || !cea608Parser2) {
22193
- return;
22194
- }
22195
21323
  const {
22196
21324
  cc,
22197
21325
  sn
22198
21326
  } = data.frag;
22199
- const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
22200
- if (!(sn === lastSn + 1 || sn === lastSn && partIndex === this.lastPartIndex + 1 || cc === this.lastCc)) {
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)) {
22201
21329
  cea608Parser1.reset();
22202
21330
  cea608Parser2.reset();
22203
21331
  }
@@ -22254,7 +21382,7 @@ class TimelineController {
22254
21382
  frag: frag
22255
21383
  });
22256
21384
  }, error => {
22257
- hls.logger.log(`Failed to parse IMSC1: ${error}`);
21385
+ logger.log(`Failed to parse IMSC1: ${error}`);
22258
21386
  hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
22259
21387
  success: false,
22260
21388
  frag: frag,
@@ -22295,7 +21423,7 @@ class TimelineController {
22295
21423
  this._fallbackToIMSC1(frag, payload);
22296
21424
  }
22297
21425
  // Something went wrong while parsing. Trigger event with success false.
22298
- hls.logger.log(`Failed to parse VTT cue: ${error}`);
21426
+ logger.log(`Failed to parse VTT cue: ${error}`);
22299
21427
  if (missingInitPTS && maxAvCC > frag.cc) {
22300
21428
  return;
22301
21429
  }
@@ -22356,7 +21484,12 @@ class TimelineController {
22356
21484
  this.captionsTracks = {};
22357
21485
  }
22358
21486
  onFragParsingUserdata(event, data) {
22359
- if (!this.enabled || !this.config.enableCEA708Captions) {
21487
+ this.initCea608Parsers();
21488
+ const {
21489
+ cea608Parser1,
21490
+ cea608Parser2
21491
+ } = this;
21492
+ if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
22360
21493
  return;
22361
21494
  }
22362
21495
  const {
@@ -22371,12 +21504,9 @@ class TimelineController {
22371
21504
  for (let i = 0; i < samples.length; i++) {
22372
21505
  const ccBytes = samples[i].bytes;
22373
21506
  if (ccBytes) {
22374
- if (!this.cea608Parser1) {
22375
- this.initCea608Parsers();
22376
- }
22377
21507
  const ccdatas = this.extractCea608Data(ccBytes);
22378
- this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
22379
- this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
21508
+ cea608Parser1.addData(samples[i].pts, ccdatas[0]);
21509
+ cea608Parser2.addData(samples[i].pts, ccdatas[1]);
22380
21510
  }
22381
21511
  }
22382
21512
  }
@@ -22572,7 +21702,7 @@ class CapLevelController {
22572
21702
  const hls = this.hls;
22573
21703
  const maxLevel = this.getMaxLevel(levels.length - 1);
22574
21704
  if (maxLevel !== this.autoLevelCapping) {
22575
- hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
21705
+ logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
22576
21706
  }
22577
21707
  hls.autoLevelCapping = maxLevel;
22578
21708
  if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
@@ -22750,10 +21880,10 @@ class FPSController {
22750
21880
  totalDroppedFrames: droppedFrames
22751
21881
  });
22752
21882
  if (droppedFPS > 0) {
22753
- // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
21883
+ // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
22754
21884
  if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
22755
21885
  let currentLevel = hls.currentLevel;
22756
- hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
21886
+ logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
22757
21887
  if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
22758
21888
  currentLevel = currentLevel - 1;
22759
21889
  hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
@@ -22785,119 +21915,31 @@ class FPSController {
22785
21915
  }
22786
21916
  }
22787
21917
 
21918
+ const LOGGER_PREFIX = '[eme]';
22788
21919
  /**
22789
21920
  * Controller to deal with encrypted media extensions (EME)
22790
21921
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
22791
21922
  *
22792
21923
  * @class
22793
- * @constructor
22794
- */
22795
- class EMEController extends Logger {
22796
- constructor(hls) {
22797
- super('eme', hls.logger);
22798
- this.hls = void 0;
22799
- this.config = void 0;
22800
- this.media = null;
22801
- this.keyFormatPromise = null;
22802
- this.keySystemAccessPromises = {};
22803
- this._requestLicenseFailureCount = 0;
22804
- this.mediaKeySessions = [];
22805
- this.keyIdToKeySessionPromise = {};
22806
- this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
22807
- this.onMediaEncrypted = event => {
22808
- const {
22809
- initDataType,
22810
- initData
22811
- } = event;
22812
- this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22813
-
22814
- // Ignore event when initData is null
22815
- if (initData === null) {
22816
- return;
22817
- }
22818
- let keyId;
22819
- let keySystemDomain;
22820
- if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22821
- // Match sinf keyId to playlist skd://keyId=
22822
- const json = bin2str(new Uint8Array(initData));
22823
- try {
22824
- const sinf = base64Decode(JSON.parse(json).sinf);
22825
- const tenc = parseSinf(new Uint8Array(sinf));
22826
- if (!tenc) {
22827
- return;
22828
- }
22829
- keyId = tenc.subarray(8, 24);
22830
- keySystemDomain = KeySystems.FAIRPLAY;
22831
- } catch (error) {
22832
- this.warn('Failed to parse sinf "encrypted" event message initData');
22833
- return;
22834
- }
22835
- } else {
22836
- // Support clear-lead key-session creation (otherwise depend on playlist keys)
22837
- const psshInfo = parsePssh(initData);
22838
- if (psshInfo === null) {
22839
- return;
22840
- }
22841
- if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22842
- keyId = psshInfo.data.subarray(8, 24);
22843
- }
22844
- keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22845
- }
22846
- if (!keySystemDomain || !keyId) {
22847
- return;
22848
- }
22849
- const keyIdHex = Hex.hexDump(keyId);
22850
- const {
22851
- keyIdToKeySessionPromise,
22852
- mediaKeySessions
22853
- } = this;
22854
- let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22855
- for (let i = 0; i < mediaKeySessions.length; i++) {
22856
- // Match playlist key
22857
- const keyContext = mediaKeySessions[i];
22858
- const decryptdata = keyContext.decryptdata;
22859
- if (decryptdata.pssh || !decryptdata.keyId) {
22860
- continue;
22861
- }
22862
- const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22863
- if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22864
- keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22865
- delete keyIdToKeySessionPromise[oldKeyIdHex];
22866
- decryptdata.pssh = new Uint8Array(initData);
22867
- decryptdata.keyId = keyId;
22868
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22869
- return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22870
- });
22871
- break;
22872
- }
22873
- }
22874
- if (!keySessionContextPromise) {
22875
- // Clear-lead key (not encountered in playlist)
22876
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22877
- keySystem,
22878
- mediaKeys
22879
- }) => {
22880
- var _keySystemToKeySystem;
22881
- this.throwIfDestroyed();
22882
- const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22883
- decryptdata.pssh = new Uint8Array(initData);
22884
- decryptdata.keyId = keyId;
22885
- return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22886
- this.throwIfDestroyed();
22887
- const keySessionContext = this.createMediaKeySessionContext({
22888
- decryptdata,
22889
- keySystem,
22890
- mediaKeys
22891
- });
22892
- return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22893
- });
22894
- });
22895
- }
22896
- keySessionContextPromise.catch(error => this.handleError(error));
22897
- };
22898
- this.onWaitingForKey = event => {
22899
- this.log(`"${event.type}" event`);
22900
- };
21924
+ * @constructor
21925
+ */
21926
+ class EMEController {
21927
+ constructor(hls) {
21928
+ this.hls = void 0;
21929
+ this.config = void 0;
21930
+ this.media = null;
21931
+ this.keyFormatPromise = null;
21932
+ this.keySystemAccessPromises = {};
21933
+ this._requestLicenseFailureCount = 0;
21934
+ this.mediaKeySessions = [];
21935
+ this.keyIdToKeySessionPromise = {};
21936
+ 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);
22901
21943
  this.hls = hls;
22902
21944
  this.config = hls.config;
22903
21945
  this.registerListeners();
@@ -22911,9 +21953,9 @@ class EMEController extends Logger {
22911
21953
  config.licenseXhrSetup = config.licenseResponseCallback = undefined;
22912
21954
  config.drmSystems = config.drmSystemOptions = {};
22913
21955
  // @ts-ignore
22914
- this.hls = this.config = this.keyIdToKeySessionPromise = null;
21956
+ this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
22915
21957
  // @ts-ignore
22916
- this.onMediaEncrypted = this.onWaitingForKey = null;
21958
+ this.config = null;
22917
21959
  }
22918
21960
  registerListeners() {
22919
21961
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -23177,6 +22219,100 @@ class EMEController extends Logger {
23177
22219
  }
23178
22220
  return this.attemptKeySystemAccess(keySystemsToAttempt);
23179
22221
  }
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
+ }
23180
22316
  attemptSetMediaKeys(keySystem, mediaKeys) {
23181
22317
  const queue = this.setMediaKeysQueue.slice();
23182
22318
  this.log(`Setting media-keys for "${keySystem}"`);
@@ -23769,6 +22905,20 @@ class SfItem {
23769
22905
  }
23770
22906
  }
23771
22907
 
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
+
23772
22922
  const DICT = 'Dict';
23773
22923
 
23774
22924
  function format(value) {
@@ -23792,27 +22942,29 @@ function throwError(action, src, type, cause) {
23792
22942
  });
23793
22943
  }
23794
22944
 
23795
- function serializeError(src, type, cause) {
23796
- return throwError('serialize', src, type, cause);
23797
- }
22945
+ const BARE_ITEM = 'Bare Item';
23798
22946
 
23799
- /**
23800
- * A class to represent structured field tokens when `Symbol` is not available.
23801
- *
23802
- * @group Structured Field
23803
- *
23804
- * @beta
23805
- */
23806
- class SfToken {
23807
- constructor(description) {
23808
- this.description = void 0;
23809
- this.description = description;
23810
- }
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;
23811
22957
  }
23812
22958
 
23813
- const BARE_ITEM = 'Bare Item';
22959
+ const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
23814
22960
 
23815
- const BOOLEAN = 'Boolean';
22961
+ const TOKEN = 'Token';
22962
+
22963
+ const KEY = 'Key';
22964
+
22965
+ function serializeError(src, type, cause) {
22966
+ return throwError('serialize', src, type, cause);
22967
+ }
23816
22968
 
23817
22969
  // 4.1.9. Serializing a Boolean
23818
22970
  //
@@ -23851,8 +23003,6 @@ function base64encode(binary) {
23851
23003
  return btoa(String.fromCharCode(...binary));
23852
23004
  }
23853
23005
 
23854
- const BYTES = 'Byte Sequence';
23855
-
23856
23006
  // 4.1.8. Serializing a Byte Sequence
23857
23007
  //
23858
23008
  // Given a Byte Sequence as input_bytes, return an ASCII string suitable
@@ -23884,12 +23034,6 @@ function serializeByteSequence(value) {
23884
23034
  return `:${base64encode(value)}:`;
23885
23035
  }
23886
23036
 
23887
- const INTEGER = 'Integer';
23888
-
23889
- function isInvalidInt(value) {
23890
- return value < -999999999999999 || 999999999999999 < value;
23891
- }
23892
-
23893
23037
  // 4.1.4. Serializing an Integer
23894
23038
  //
23895
23039
  // Given an Integer as input_integer, return an ASCII string suitable
@@ -23955,8 +23099,6 @@ function roundToEven(value, precision) {
23955
23099
  }
23956
23100
  }
23957
23101
 
23958
- const DECIMAL = 'Decimal';
23959
-
23960
23102
  // 4.1.5. Serializing a Decimal
23961
23103
  //
23962
23104
  // Given a decimal number as input_decimal, return an ASCII string
@@ -24002,8 +23144,6 @@ function serializeDecimal(value) {
24002
23144
 
24003
23145
  const STRING = 'String';
24004
23146
 
24005
- const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
24006
-
24007
23147
  // 4.1.6. Serializing a String
24008
23148
  //
24009
23149
  // Given a String as input_string, return an ASCII string suitable for
@@ -24039,8 +23179,6 @@ function symbolToStr(symbol) {
24039
23179
  return symbol.description || symbol.toString().slice(7, -1);
24040
23180
  }
24041
23181
 
24042
- const TOKEN = 'Token';
24043
-
24044
23182
  function serializeToken(token) {
24045
23183
  const value = symbolToStr(token);
24046
23184
  if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
@@ -24108,8 +23246,6 @@ function serializeBareItem(value) {
24108
23246
  }
24109
23247
  }
24110
23248
 
24111
- const KEY = 'Key';
24112
-
24113
23249
  // 4.1.1.3. Serializing a Key
24114
23250
  //
24115
23251
  // Given a key as input_key, return an ASCII string suitable for use in
@@ -24351,6 +23487,36 @@ function urlToRelativePath(url, base) {
24351
23487
  return toPath.join('/');
24352
23488
  }
24353
23489
 
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
+
24354
23520
  const toRounded = value => Math.round(value);
24355
23521
  const toUrlSafe = (value, options) => {
24356
23522
  if (options != null && options.baseUrl) {
@@ -24576,36 +23742,6 @@ function appendCmcdQuery(url, cmcd, options) {
24576
23742
  return `${url}${separator}${query}`;
24577
23743
  }
24578
23744
 
24579
- /**
24580
- * Generate a random v4 UUID
24581
- *
24582
- * @returns A random v4 UUID
24583
- *
24584
- * @group Utils
24585
- *
24586
- * @beta
24587
- */
24588
- function uuid() {
24589
- try {
24590
- return crypto.randomUUID();
24591
- } catch (error) {
24592
- try {
24593
- const url = URL.createObjectURL(new Blob());
24594
- const uuid = url.toString();
24595
- URL.revokeObjectURL(url);
24596
- return uuid.slice(uuid.lastIndexOf('/') + 1);
24597
- } catch (error) {
24598
- let dt = new Date().getTime();
24599
- const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
24600
- const r = (dt + Math.random() * 16) % 16 | 0;
24601
- dt = Math.floor(dt / 16);
24602
- return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
24603
- });
24604
- return uuid;
24605
- }
24606
- }
24607
- }
24608
-
24609
23745
  /**
24610
23746
  * Controller to deal with Common Media Client Data (CMCD)
24611
23747
  * @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
@@ -24649,7 +23785,7 @@ class CMCDController {
24649
23785
  su: !this.initialized
24650
23786
  });
24651
23787
  } catch (error) {
24652
- this.hls.logger.warn('Could not generate manifest CMCD data.', error);
23788
+ logger.warn('Could not generate manifest CMCD data.', error);
24653
23789
  }
24654
23790
  };
24655
23791
  /**
@@ -24669,15 +23805,9 @@ class CMCDController {
24669
23805
  data.tb = this.getTopBandwidth(ot) / 1000;
24670
23806
  data.bl = this.getBufferLength(ot);
24671
23807
  }
24672
- const next = this.getNextFrag(fragment);
24673
- if (next) {
24674
- if (next.url && next.url !== fragment.url) {
24675
- data.nor = next.url;
24676
- }
24677
- }
24678
23808
  this.apply(context, data);
24679
23809
  } catch (error) {
24680
- this.hls.logger.warn('Could not generate segment CMCD data.', error);
23810
+ logger.warn('Could not generate segment CMCD data.', error);
24681
23811
  }
24682
23812
  };
24683
23813
  this.hls = hls;
@@ -24767,7 +23897,7 @@ class CMCDController {
24767
23897
  data.su = this.buffering;
24768
23898
  }
24769
23899
 
24770
- // TODO: Implement rtp, nrr, dl
23900
+ // TODO: Implement rtp, nrr, nor, dl
24771
23901
 
24772
23902
  const {
24773
23903
  includeKeys
@@ -24778,28 +23908,15 @@ class CMCDController {
24778
23908
  return acc;
24779
23909
  }, {});
24780
23910
  }
24781
- const options = {
24782
- baseUrl: context.url
24783
- };
24784
23911
  if (this.useHeaders) {
24785
23912
  if (!context.headers) {
24786
23913
  context.headers = {};
24787
23914
  }
24788
- appendCmcdHeaders(context.headers, data, options);
23915
+ appendCmcdHeaders(context.headers, data);
24789
23916
  } else {
24790
- context.url = appendCmcdQuery(context.url, data, options);
24791
- }
24792
- }
24793
- getNextFrag(fragment) {
24794
- var _this$hls$levels$frag;
24795
- const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
24796
- if (levelDetails) {
24797
- const index = fragment.sn - levelDetails.startSN;
24798
- return levelDetails.fragments[index + 1];
23917
+ context.url = appendCmcdQuery(context.url, data);
24799
23918
  }
24800
- return undefined;
24801
23919
  }
24802
-
24803
23920
  /**
24804
23921
  * The CMCD object type.
24805
23922
  */
@@ -24928,10 +24045,10 @@ class CMCDController {
24928
24045
  }
24929
24046
 
24930
24047
  const PATHWAY_PENALTY_DURATION_MS = 300000;
24931
- class ContentSteeringController extends Logger {
24048
+ class ContentSteeringController {
24932
24049
  constructor(hls) {
24933
- super('content-steering', hls.logger);
24934
24050
  this.hls = void 0;
24051
+ this.log = void 0;
24935
24052
  this.loader = null;
24936
24053
  this.uri = null;
24937
24054
  this.pathwayId = '.';
@@ -24946,6 +24063,7 @@ class ContentSteeringController extends Logger {
24946
24063
  this.subtitleTracks = null;
24947
24064
  this.penalizedPathways = {};
24948
24065
  this.hls = hls;
24066
+ this.log = logger.log.bind(logger, `[content-steering]:`);
24949
24067
  this.registerListeners();
24950
24068
  }
24951
24069
  registerListeners() {
@@ -25069,7 +24187,7 @@ class ContentSteeringController extends Logger {
25069
24187
  errorAction.resolved = this.pathwayId !== errorPathway;
25070
24188
  }
25071
24189
  if (!errorAction.resolved) {
25072
- 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)}`);
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)}`);
25073
24191
  }
25074
24192
  }
25075
24193
  }
@@ -25240,7 +24358,7 @@ class ContentSteeringController extends Logger {
25240
24358
  onSuccess: (response, stats, context, networkDetails) => {
25241
24359
  this.log(`Loaded steering manifest: "${url}"`);
25242
24360
  const steeringData = response.data;
25243
- if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
24361
+ if (steeringData.VERSION !== 1) {
25244
24362
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
25245
24363
  return;
25246
24364
  }
@@ -26210,7 +25328,7 @@ function timelineConfig() {
26210
25328
  /**
26211
25329
  * @ignore
26212
25330
  */
26213
- function mergeConfig(defaultConfig, userConfig, logger) {
25331
+ function mergeConfig(defaultConfig, userConfig) {
26214
25332
  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
26215
25333
  throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
26216
25334
  }
@@ -26280,7 +25398,7 @@ function deepCpy(obj) {
26280
25398
  /**
26281
25399
  * @ignore
26282
25400
  */
26283
- function enableStreamingMode(config, logger) {
25401
+ function enableStreamingMode(config) {
26284
25402
  const currentLoader = config.loader;
26285
25403
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
26286
25404
  // If a developer has configured their own loader, respect that choice
@@ -26297,9 +25415,10 @@ function enableStreamingMode(config, logger) {
26297
25415
  }
26298
25416
  }
26299
25417
 
25418
+ let chromeOrFirefox;
26300
25419
  class LevelController extends BasePlaylistController {
26301
25420
  constructor(hls, contentSteeringController) {
26302
- super(hls, 'level-controller');
25421
+ super(hls, '[level-controller]');
26303
25422
  this._levels = [];
26304
25423
  this._firstLevel = -1;
26305
25424
  this._maxAutoLevel = -1;
@@ -26370,15 +25489,23 @@ class LevelController extends BasePlaylistController {
26370
25489
  let videoCodecFound = false;
26371
25490
  let audioCodecFound = false;
26372
25491
  data.levels.forEach(levelParsed => {
26373
- var _videoCodec;
25492
+ var _audioCodec, _videoCodec;
26374
25493
  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
26375
25497
  let {
26376
25498
  audioCodec,
26377
25499
  videoCodec
26378
25500
  } = 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
+ }
26379
25507
  if (audioCodec) {
26380
- // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
26381
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
25508
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
26382
25509
  }
26383
25510
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
26384
25511
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -26720,12 +25847,7 @@ class LevelController extends BasePlaylistController {
26720
25847
  if (curLevel.fragmentError === 0) {
26721
25848
  curLevel.loadError = 0;
26722
25849
  }
26723
- // Ignore matching details populated by loading a Media Playlist directly
26724
- let previousDetails = curLevel.details;
26725
- if (previousDetails === data.details && previousDetails.advanced) {
26726
- previousDetails = undefined;
26727
- }
26728
- this.playlistLoaded(level, data, previousDetails);
25850
+ this.playlistLoaded(level, data, curLevel.details);
26729
25851
  } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
26730
25852
  // received a delta playlist update that cannot be merged
26731
25853
  details.deltaUpdateFailed = true;
@@ -26969,8 +26091,6 @@ class KeyLoader {
26969
26091
  }
26970
26092
  return this.loadKeyEME(keyInfo, frag);
26971
26093
  case 'AES-128':
26972
- case 'AES-256':
26973
- case 'AES-256-CTR':
26974
26094
  return this.loadKeyHTTP(keyInfo, frag);
26975
26095
  default:
26976
26096
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -27108,9 +26228,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
27108
26228
  const MAX_START_GAP_JUMP = 2.0;
27109
26229
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
27110
26230
  const SKIP_BUFFER_RANGE_START = 0.05;
27111
- class GapController extends Logger {
26231
+ class GapController {
27112
26232
  constructor(config, media, fragmentTracker, hls) {
27113
- super('gap-controller', hls.logger);
27114
26233
  this.config = void 0;
27115
26234
  this.media = null;
27116
26235
  this.fragmentTracker = void 0;
@@ -27120,7 +26239,6 @@ class GapController extends Logger {
27120
26239
  this.stalled = null;
27121
26240
  this.moved = false;
27122
26241
  this.seeking = false;
27123
- this.ended = 0;
27124
26242
  this.config = config;
27125
26243
  this.media = media;
27126
26244
  this.fragmentTracker = fragmentTracker;
@@ -27138,7 +26256,7 @@ class GapController extends Logger {
27138
26256
  *
27139
26257
  * @param lastCurrentTime - Previously read playhead position
27140
26258
  */
27141
- poll(lastCurrentTime, activeFrag, levelDetails, state) {
26259
+ poll(lastCurrentTime, activeFrag) {
27142
26260
  const {
27143
26261
  config,
27144
26262
  media,
@@ -27157,7 +26275,6 @@ class GapController extends Logger {
27157
26275
 
27158
26276
  // The playhead is moving, no-op
27159
26277
  if (currentTime !== lastCurrentTime) {
27160
- this.ended = 0;
27161
26278
  this.moved = true;
27162
26279
  if (!seeking) {
27163
26280
  this.nudgeRetry = 0;
@@ -27166,7 +26283,7 @@ class GapController extends Logger {
27166
26283
  // The playhead is now moving, but was previously stalled
27167
26284
  if (this.stallReported) {
27168
26285
  const _stalledDuration = self.performance.now() - stalled;
27169
- this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
26286
+ logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
27170
26287
  this.stallReported = false;
27171
26288
  }
27172
26289
  this.stalled = null;
@@ -27202,6 +26319,7 @@ class GapController extends Logger {
27202
26319
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
27203
26320
  // The addition poll gives the browser a chance to jump the gap for us
27204
26321
  if (!this.moved && this.stalled !== null) {
26322
+ var _level$details;
27205
26323
  // There is no playable buffer (seeked, waiting for buffer)
27206
26324
  const isBuffered = bufferInfo.len > 0;
27207
26325
  if (!isBuffered && !nextStart) {
@@ -27213,8 +26331,9 @@ class GapController extends Logger {
27213
26331
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
27214
26332
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
27215
26333
  // that begins over 1 target duration after the video start position.
27216
- const isLive = !!(levelDetails != null && levelDetails.live);
27217
- const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
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;
27218
26337
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
27219
26338
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
27220
26339
  if (!media.paused) {
@@ -27232,17 +26351,6 @@ class GapController extends Logger {
27232
26351
  }
27233
26352
  const stalledDuration = tnow - stalled;
27234
26353
  if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
27235
- // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
27236
- if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
27237
- if (stalledDuration < 1000 || this.ended) {
27238
- return;
27239
- }
27240
- this.ended = currentTime;
27241
- this.hls.trigger(Events.MEDIA_ENDED, {
27242
- stalled: true
27243
- });
27244
- return;
27245
- }
27246
26354
  // Report stalling after trying to fix
27247
26355
  this._reportStall(bufferInfo);
27248
26356
  if (!this.media) {
@@ -27286,7 +26394,7 @@ class GapController extends Logger {
27286
26394
  // needs to cross some sort of threshold covering all source-buffers content
27287
26395
  // to start playing properly.
27288
26396
  if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
27289
- this.warn('Trying to nudge playhead over buffer-hole');
26397
+ logger.warn('Trying to nudge playhead over buffer-hole');
27290
26398
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
27291
26399
  // We only try to jump the hole if it's under the configured size
27292
26400
  // Reset stalled so to rearm watchdog timer
@@ -27310,7 +26418,7 @@ class GapController extends Logger {
27310
26418
  // Report stalled error once
27311
26419
  this.stallReported = true;
27312
26420
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
27313
- this.warn(error.message);
26421
+ logger.warn(error.message);
27314
26422
  hls.trigger(Events.ERROR, {
27315
26423
  type: ErrorTypes.MEDIA_ERROR,
27316
26424
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -27378,7 +26486,7 @@ class GapController extends Logger {
27378
26486
  }
27379
26487
  }
27380
26488
  const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
27381
- this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
26489
+ logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
27382
26490
  this.moved = true;
27383
26491
  this.stalled = null;
27384
26492
  media.currentTime = targetTime;
@@ -27419,7 +26527,7 @@ class GapController extends Logger {
27419
26527
  const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
27420
26528
  // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
27421
26529
  const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
27422
- this.warn(error.message);
26530
+ logger.warn(error.message);
27423
26531
  media.currentTime = targetTime;
27424
26532
  hls.trigger(Events.ERROR, {
27425
26533
  type: ErrorTypes.MEDIA_ERROR,
@@ -27429,7 +26537,7 @@ class GapController extends Logger {
27429
26537
  });
27430
26538
  } else {
27431
26539
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
27432
- this.error(error.message);
26540
+ logger.error(error.message);
27433
26541
  hls.trigger(Events.ERROR, {
27434
26542
  type: ErrorTypes.MEDIA_ERROR,
27435
26543
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -27444,7 +26552,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
27444
26552
 
27445
26553
  class StreamController extends BaseStreamController {
27446
26554
  constructor(hls, fragmentTracker, keyLoader) {
27447
- super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
26555
+ super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
27448
26556
  this.audioCodecSwap = false;
27449
26557
  this.gapController = null;
27450
26558
  this.level = -1;
@@ -27452,43 +26560,27 @@ class StreamController extends BaseStreamController {
27452
26560
  this.altAudio = false;
27453
26561
  this.audioOnly = false;
27454
26562
  this.fragPlaying = null;
26563
+ this.onvplaying = null;
26564
+ this.onvseeked = null;
27455
26565
  this.fragLastKbps = 0;
27456
26566
  this.couldBacktrack = false;
27457
26567
  this.backtrackFragment = null;
27458
26568
  this.audioCodecSwitch = false;
27459
26569
  this.videoBuffer = null;
27460
- this.onMediaPlaying = () => {
27461
- // tick to speed up FRAG_CHANGED triggering
27462
- this.tick();
27463
- };
27464
- this.onMediaSeeked = () => {
27465
- const media = this.media;
27466
- const currentTime = media ? media.currentTime : null;
27467
- if (isFiniteNumber(currentTime)) {
27468
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
27469
- }
27470
-
27471
- // If seeked was issued before buffer was appended do not tick immediately
27472
- const bufferInfo = this.getMainFwdBufferInfo();
27473
- if (bufferInfo === null || bufferInfo.len === 0) {
27474
- this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
27475
- return;
27476
- }
27477
-
27478
- // tick to speed up FRAG_CHANGED triggering
27479
- this.tick();
27480
- };
27481
- this.registerListeners();
26570
+ this._registerListeners();
27482
26571
  }
27483
- registerListeners() {
27484
- super.registerListeners();
26572
+ _registerListeners() {
27485
26573
  const {
27486
26574
  hls
27487
26575
  } = 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);
27488
26579
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
27489
26580
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
27490
26581
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
27491
26582
  hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26583
+ hls.on(Events.ERROR, this.onError, this);
27492
26584
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
27493
26585
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
27494
26586
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -27496,14 +26588,17 @@ class StreamController extends BaseStreamController {
27496
26588
  hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
27497
26589
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
27498
26590
  }
27499
- unregisterListeners() {
27500
- super.unregisterListeners();
26591
+ _unregisterListeners() {
27501
26592
  const {
27502
26593
  hls
27503
26594
  } = 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);
27504
26598
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
27505
26599
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
27506
26600
  hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26601
+ hls.off(Events.ERROR, this.onError, this);
27507
26602
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
27508
26603
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
27509
26604
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -27512,9 +26607,7 @@ class StreamController extends BaseStreamController {
27512
26607
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
27513
26608
  }
27514
26609
  onHandlerDestroying() {
27515
- // @ts-ignore
27516
- this.onMediaPlaying = this.onMediaSeeked = null;
27517
- this.unregisterListeners();
26610
+ this._unregisterListeners();
27518
26611
  super.onHandlerDestroying();
27519
26612
  }
27520
26613
  startLoad(startPosition) {
@@ -27618,15 +26711,11 @@ class StreamController extends BaseStreamController {
27618
26711
  levels,
27619
26712
  media
27620
26713
  } = this;
27621
- const {
27622
- config,
27623
- nextLoadLevel: level
27624
- } = hls;
27625
26714
 
27626
26715
  // if start level not parsed yet OR
27627
26716
  // if video not attached AND start fragment already requested OR start frag prefetch not enabled
27628
26717
  // exit loop, as we either need more info (level not parsed) or we need media to be attached to load new fragment
27629
- if (levelLastLoaded === null || !media && (this.startFragRequested || !config.startFragPrefetch)) {
26718
+ if (levelLastLoaded === null || !media && (this.startFragRequested || !hls.config.startFragPrefetch)) {
27630
26719
  return;
27631
26720
  }
27632
26721
 
@@ -27634,7 +26723,8 @@ class StreamController extends BaseStreamController {
27634
26723
  if (this.altAudio && this.audioOnly) {
27635
26724
  return;
27636
26725
  }
27637
- if (!this.buffering || !(levels != null && levels[level])) {
26726
+ const level = hls.nextLoadLevel;
26727
+ if (!(levels != null && levels[level])) {
27638
26728
  return;
27639
26729
  }
27640
26730
  const levelInfo = levels[level];
@@ -27842,17 +26932,20 @@ class StreamController extends BaseStreamController {
27842
26932
  onMediaAttached(event, data) {
27843
26933
  super.onMediaAttached(event, data);
27844
26934
  const media = data.media;
27845
- media.addEventListener('playing', this.onMediaPlaying);
27846
- media.addEventListener('seeked', this.onMediaSeeked);
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);
27847
26939
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
27848
26940
  }
27849
26941
  onMediaDetaching() {
27850
26942
  const {
27851
26943
  media
27852
26944
  } = this;
27853
- if (media) {
27854
- media.removeEventListener('playing', this.onMediaPlaying);
27855
- media.removeEventListener('seeked', this.onMediaSeeked);
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;
27856
26949
  this.videoBuffer = null;
27857
26950
  }
27858
26951
  this.fragPlaying = null;
@@ -27862,6 +26955,27 @@ class StreamController extends BaseStreamController {
27862
26955
  }
27863
26956
  super.onMediaDetaching();
27864
26957
  }
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
+ }
27865
26979
  onManifestLoading() {
27866
26980
  // reset buffer on manifest loading
27867
26981
  this.log('Trigger BUFFER_RESET');
@@ -28153,10 +27267,8 @@ class StreamController extends BaseStreamController {
28153
27267
  }
28154
27268
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
28155
27269
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
28156
- const state = this.state;
28157
- const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
28158
- const levelDetails = this.getLevelDetails();
28159
- gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
27270
+ const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
27271
+ gapController.poll(this.lastCurrentTime, activeFrag);
28160
27272
  }
28161
27273
  this.lastCurrentTime = media.currentTime;
28162
27274
  }
@@ -28594,7 +27706,7 @@ class Hls {
28594
27706
  * Get the video-dev/hls.js package version.
28595
27707
  */
28596
27708
  static get version() {
28597
- return "1.5.6-0.canary.9999";
27709
+ return "1.5.6";
28598
27710
  }
28599
27711
 
28600
27712
  /**
@@ -28657,12 +27769,9 @@ class Hls {
28657
27769
  * The configuration object provided on player instantiation.
28658
27770
  */
28659
27771
  this.userConfig = void 0;
28660
- /**
28661
- * The logger functions used by this player instance, configured on player instantiation.
28662
- */
28663
- this.logger = void 0;
28664
27772
  this.coreComponents = void 0;
28665
27773
  this.networkControllers = void 0;
27774
+ this.started = false;
28666
27775
  this._emitter = new EventEmitter();
28667
27776
  this._autoLevelCapping = -1;
28668
27777
  this._maxHdcpLevel = null;
@@ -28679,11 +27788,11 @@ class Hls {
28679
27788
  this._media = null;
28680
27789
  this.url = null;
28681
27790
  this.triggeringException = void 0;
28682
- const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
28683
- const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
27791
+ enableLogs(userConfig.debug || false, 'Hls instance');
27792
+ const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
28684
27793
  this.userConfig = userConfig;
28685
27794
  if (config.progressive) {
28686
- enableStreamingMode(config, logger);
27795
+ enableStreamingMode(config);
28687
27796
  }
28688
27797
 
28689
27798
  // core controllers and network loaders
@@ -28782,7 +27891,7 @@ class Hls {
28782
27891
  try {
28783
27892
  return this.emit(event, event, eventObject);
28784
27893
  } catch (error) {
28785
- this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
27894
+ logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
28786
27895
  // Prevent recursion in error event handlers that throw #5497
28787
27896
  if (!this.triggeringException) {
28788
27897
  this.triggeringException = true;
@@ -28808,7 +27917,7 @@ class Hls {
28808
27917
  * Dispose of the instance
28809
27918
  */
28810
27919
  destroy() {
28811
- this.logger.log('destroy');
27920
+ logger.log('destroy');
28812
27921
  this.trigger(Events.DESTROYING, undefined);
28813
27922
  this.detachMedia();
28814
27923
  this.removeAllListeners();
@@ -28829,7 +27938,7 @@ class Hls {
28829
27938
  * Attaches Hls.js to a media element
28830
27939
  */
28831
27940
  attachMedia(media) {
28832
- this.logger.log('attachMedia');
27941
+ logger.log('attachMedia');
28833
27942
  this._media = media;
28834
27943
  this.trigger(Events.MEDIA_ATTACHING, {
28835
27944
  media: media
@@ -28840,7 +27949,7 @@ class Hls {
28840
27949
  * Detach Hls.js from the media
28841
27950
  */
28842
27951
  detachMedia() {
28843
- this.logger.log('detachMedia');
27952
+ logger.log('detachMedia');
28844
27953
  this.trigger(Events.MEDIA_DETACHING, undefined);
28845
27954
  this._media = null;
28846
27955
  }
@@ -28857,7 +27966,7 @@ class Hls {
28857
27966
  });
28858
27967
  this._autoLevelCapping = -1;
28859
27968
  this._maxHdcpLevel = null;
28860
- this.logger.log(`loadSource:${loadingSource}`);
27969
+ logger.log(`loadSource:${loadingSource}`);
28861
27970
  if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
28862
27971
  this.detachMedia();
28863
27972
  this.attachMedia(media);
@@ -28876,7 +27985,8 @@ class Hls {
28876
27985
  * Defaults to -1 (None: starts from earliest point)
28877
27986
  */
28878
27987
  startLoad(startPosition = -1) {
28879
- this.logger.log(`startLoad(${startPosition})`);
27988
+ logger.log(`startLoad(${startPosition})`);
27989
+ this.started = true;
28880
27990
  this.networkControllers.forEach(controller => {
28881
27991
  controller.startLoad(startPosition);
28882
27992
  });
@@ -28886,31 +27996,34 @@ class Hls {
28886
27996
  * Stop loading of any stream data.
28887
27997
  */
28888
27998
  stopLoad() {
28889
- this.logger.log('stopLoad');
27999
+ logger.log('stopLoad');
28000
+ this.started = false;
28890
28001
  this.networkControllers.forEach(controller => {
28891
28002
  controller.stopLoad();
28892
28003
  });
28893
28004
  }
28894
28005
 
28895
28006
  /**
28896
- * Resumes stream controller segment loading after `pauseBuffering` has been called.
28007
+ * Resumes stream controller segment loading if previously started.
28897
28008
  */
28898
28009
  resumeBuffering() {
28899
- this.networkControllers.forEach(controller => {
28900
- if (controller.resumeBuffering) {
28901
- controller.resumeBuffering();
28902
- }
28903
- });
28010
+ if (this.started) {
28011
+ this.networkControllers.forEach(controller => {
28012
+ if ('fragmentLoader' in controller) {
28013
+ controller.startLoad(-1);
28014
+ }
28015
+ });
28016
+ }
28904
28017
  }
28905
28018
 
28906
28019
  /**
28907
- * Prevents stream controller from loading new segments until `resumeBuffering` is called.
28020
+ * Stops stream controller segment loading without changing 'started' state like stopLoad().
28908
28021
  * This allows for media buffering to be paused without interupting playlist loading.
28909
28022
  */
28910
28023
  pauseBuffering() {
28911
28024
  this.networkControllers.forEach(controller => {
28912
- if (controller.pauseBuffering) {
28913
- controller.pauseBuffering();
28025
+ if ('fragmentLoader' in controller) {
28026
+ controller.stopLoad();
28914
28027
  }
28915
28028
  });
28916
28029
  }
@@ -28919,7 +28032,7 @@ class Hls {
28919
28032
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
28920
28033
  */
28921
28034
  swapAudioCodec() {
28922
- this.logger.log('swapAudioCodec');
28035
+ logger.log('swapAudioCodec');
28923
28036
  this.streamController.swapAudioCodec();
28924
28037
  }
28925
28038
 
@@ -28930,7 +28043,7 @@ class Hls {
28930
28043
  * Automatic recovery of media-errors by this process is configurable.
28931
28044
  */
28932
28045
  recoverMediaError() {
28933
- this.logger.log('recoverMediaError');
28046
+ logger.log('recoverMediaError');
28934
28047
  const media = this._media;
28935
28048
  this.detachMedia();
28936
28049
  if (media) {
@@ -28960,7 +28073,7 @@ class Hls {
28960
28073
  * 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.
28961
28074
  */
28962
28075
  set currentLevel(newLevel) {
28963
- this.logger.log(`set currentLevel:${newLevel}`);
28076
+ logger.log(`set currentLevel:${newLevel}`);
28964
28077
  this.levelController.manualLevel = newLevel;
28965
28078
  this.streamController.immediateLevelSwitch();
28966
28079
  }
@@ -28979,7 +28092,7 @@ class Hls {
28979
28092
  * @param newLevel - Pass -1 for automatic level selection
28980
28093
  */
28981
28094
  set nextLevel(newLevel) {
28982
- this.logger.log(`set nextLevel:${newLevel}`);
28095
+ logger.log(`set nextLevel:${newLevel}`);
28983
28096
  this.levelController.manualLevel = newLevel;
28984
28097
  this.streamController.nextLevelSwitch();
28985
28098
  }
@@ -28998,7 +28111,7 @@ class Hls {
28998
28111
  * @param newLevel - Pass -1 for automatic level selection
28999
28112
  */
29000
28113
  set loadLevel(newLevel) {
29001
- this.logger.log(`set loadLevel:${newLevel}`);
28114
+ logger.log(`set loadLevel:${newLevel}`);
29002
28115
  this.levelController.manualLevel = newLevel;
29003
28116
  }
29004
28117
 
@@ -29029,7 +28142,7 @@ class Hls {
29029
28142
  * Sets "first-level", see getter.
29030
28143
  */
29031
28144
  set firstLevel(newLevel) {
29032
- this.logger.log(`set firstLevel:${newLevel}`);
28145
+ logger.log(`set firstLevel:${newLevel}`);
29033
28146
  this.levelController.firstLevel = newLevel;
29034
28147
  }
29035
28148
 
@@ -29054,7 +28167,7 @@ class Hls {
29054
28167
  * (determined from download of first segment)
29055
28168
  */
29056
28169
  set startLevel(newLevel) {
29057
- this.logger.log(`set startLevel:${newLevel}`);
28170
+ logger.log(`set startLevel:${newLevel}`);
29058
28171
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
29059
28172
  if (newLevel !== -1) {
29060
28173
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -29129,7 +28242,7 @@ class Hls {
29129
28242
  */
29130
28243
  set autoLevelCapping(newLevel) {
29131
28244
  if (this._autoLevelCapping !== newLevel) {
29132
- this.logger.log(`set autoLevelCapping:${newLevel}`);
28245
+ logger.log(`set autoLevelCapping:${newLevel}`);
29133
28246
  this._autoLevelCapping = newLevel;
29134
28247
  this.levelController.checkMaxAutoUpdated();
29135
28248
  }