hls.js 1.5.2-0.canary.9934 → 1.5.3

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 (60) hide show
  1. package/dist/hls-demo.js +0 -5
  2. package/dist/hls-demo.js.map +1 -1
  3. package/dist/hls.js +757 -883
  4. package/dist/hls.js.d.ts +47 -56
  5. package/dist/hls.js.map +1 -1
  6. package/dist/hls.light.js +477 -600
  7. package/dist/hls.light.js.map +1 -1
  8. package/dist/hls.light.min.js +1 -1
  9. package/dist/hls.light.min.js.map +1 -1
  10. package/dist/hls.light.mjs +335 -446
  11. package/dist/hls.light.mjs.map +1 -1
  12. package/dist/hls.min.js +1 -1
  13. package/dist/hls.min.js.map +1 -1
  14. package/dist/hls.mjs +572 -681
  15. package/dist/hls.mjs.map +1 -1
  16. package/dist/hls.worker.js +1 -1
  17. package/dist/hls.worker.js.map +1 -1
  18. package/package.json +11 -11
  19. package/src/config.ts +2 -3
  20. package/src/controller/abr-controller.ts +22 -22
  21. package/src/controller/audio-stream-controller.ts +14 -11
  22. package/src/controller/audio-track-controller.ts +1 -1
  23. package/src/controller/base-playlist-controller.ts +7 -7
  24. package/src/controller/base-stream-controller.ts +29 -47
  25. package/src/controller/buffer-controller.ts +11 -10
  26. package/src/controller/cap-level-controller.ts +2 -1
  27. package/src/controller/cmcd-controller.ts +3 -25
  28. package/src/controller/content-steering-controller.ts +6 -8
  29. package/src/controller/eme-controller.ts +22 -9
  30. package/src/controller/error-controller.ts +8 -6
  31. package/src/controller/fps-controller.ts +3 -2
  32. package/src/controller/gap-controller.ts +16 -43
  33. package/src/controller/latency-controller.ts +11 -9
  34. package/src/controller/level-controller.ts +17 -5
  35. package/src/controller/stream-controller.ts +31 -24
  36. package/src/controller/subtitle-stream-controller.ts +14 -13
  37. package/src/controller/subtitle-track-controller.ts +3 -5
  38. package/src/controller/timeline-controller.ts +30 -23
  39. package/src/crypt/aes-crypto.ts +2 -21
  40. package/src/crypt/decrypter.ts +18 -32
  41. package/src/crypt/fast-aes-key.ts +5 -24
  42. package/src/demux/audio/adts.ts +4 -9
  43. package/src/demux/sample-aes.ts +0 -2
  44. package/src/demux/transmuxer-interface.ts +12 -4
  45. package/src/demux/transmuxer-worker.ts +4 -4
  46. package/src/demux/transmuxer.ts +3 -16
  47. package/src/demux/tsdemuxer.ts +17 -12
  48. package/src/events.ts +0 -7
  49. package/src/hls.ts +20 -33
  50. package/src/loader/fragment-loader.ts +2 -9
  51. package/src/loader/key-loader.ts +0 -2
  52. package/src/loader/level-key.ts +9 -10
  53. package/src/remux/mp4-remuxer.ts +4 -20
  54. package/src/task-loop.ts +2 -5
  55. package/src/types/demuxer.ts +0 -1
  56. package/src/types/events.ts +0 -4
  57. package/src/utils/codecs.ts +4 -33
  58. package/src/utils/logger.ts +24 -53
  59. package/src/crypt/decrypter-aes-mode.ts +0 -4
  60. 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,23 +369,6 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
370
369
  return ErrorDetails;
371
370
  }({});
372
371
 
373
- class Logger {
374
- constructor(label, logger) {
375
- this.trace = void 0;
376
- this.debug = void 0;
377
- this.log = void 0;
378
- this.warn = void 0;
379
- this.info = void 0;
380
- this.error = void 0;
381
- const lb = `[${label}]:`;
382
- this.trace = noop;
383
- this.debug = logger.debug.bind(null, lb);
384
- this.log = logger.log.bind(null, lb);
385
- this.warn = logger.warn.bind(null, lb);
386
- this.info = logger.info.bind(null, lb);
387
- this.error = logger.error.bind(null, lb);
388
- }
389
- }
390
372
  const noop = function noop() {};
391
373
  const fakeLogger = {
392
374
  trace: noop,
@@ -396,9 +378,7 @@ const fakeLogger = {
396
378
  info: noop,
397
379
  error: noop
398
380
  };
399
- function createLogger() {
400
- return _extends({}, fakeLogger);
401
- }
381
+ let exportedLogger = fakeLogger;
402
382
 
403
383
  // let lastCallTime;
404
384
  // function formatMsgWithTimeInfo(type, msg) {
@@ -409,36 +389,35 @@ function createLogger() {
409
389
  // return msg;
410
390
  // }
411
391
 
412
- function consolePrintFn(type, id) {
392
+ function consolePrintFn(type) {
413
393
  const func = self.console[type];
414
- return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
394
+ if (func) {
395
+ return func.bind(self.console, `[${type}] >`);
396
+ }
397
+ return noop;
415
398
  }
416
- function getLoggerFn(key, debugConfig, id) {
417
- return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
399
+ function exportLoggerFunctions(debugConfig, ...functions) {
400
+ functions.forEach(function (type) {
401
+ exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
402
+ });
418
403
  }
419
- let exportedLogger = createLogger();
420
- function enableLogs(debugConfig, context, id) {
404
+ function enableLogs(debugConfig, id) {
421
405
  // check that console is available
422
- const newLogger = createLogger();
423
406
  if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
424
- const keys = [
407
+ exportLoggerFunctions(debugConfig,
425
408
  // Remove out from list here to hard-disable a log-level
426
409
  // 'trace',
427
- 'debug', 'log', 'info', 'warn', 'error'];
428
- keys.forEach(key => {
429
- newLogger[key] = getLoggerFn(key, debugConfig, id);
430
- });
410
+ 'debug', 'log', 'info', 'warn', 'error');
431
411
  // Some browsers don't allow to use bind on console object anyway
432
412
  // fallback to default if needed
433
413
  try {
434
- newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.2-0.canary.9934"}`);
414
+ exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.3"}`);
435
415
  } catch (e) {
436
- /* log fn threw an exception. All logger methods are no-ops. */
437
- return createLogger();
416
+ exportedLogger = fakeLogger;
438
417
  }
418
+ } else {
419
+ exportedLogger = fakeLogger;
439
420
  }
440
- exportedLogger = newLogger;
441
- return newLogger;
442
421
  }
443
422
  const logger = exportedLogger;
444
423
 
@@ -1057,26 +1036,6 @@ function strToUtf8array(str) {
1057
1036
  return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
1058
1037
  }
1059
1038
 
1060
- var DecrypterAesMode = {
1061
- cbc: 0,
1062
- ctr: 1
1063
- };
1064
-
1065
- function isFullSegmentEncryption(method) {
1066
- return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
1067
- }
1068
- function getAesModeFromFullSegmentMethod(method) {
1069
- switch (method) {
1070
- case 'AES-128':
1071
- case 'AES-256':
1072
- return DecrypterAesMode.cbc;
1073
- case 'AES-256-CTR':
1074
- return DecrypterAesMode.ctr;
1075
- default:
1076
- throw new Error(`invalid full segment method ${method}`);
1077
- }
1078
- }
1079
-
1080
1039
  /** returns `undefined` is `self` is missing, e.g. in node */
1081
1040
  const optionalSelf = typeof self !== 'undefined' ? self : undefined;
1082
1041
 
@@ -2715,12 +2674,12 @@ class LevelKey {
2715
2674
  this.keyFormatVersions = formatversions;
2716
2675
  this.iv = iv;
2717
2676
  this.encrypted = method ? method !== 'NONE' : false;
2718
- this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
2677
+ this.isCommonEncryption = this.encrypted && method !== 'AES-128';
2719
2678
  }
2720
2679
  isSupported() {
2721
2680
  // If it's Segment encryption or No encryption, just select that key system
2722
2681
  if (this.method) {
2723
- if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
2682
+ if (this.method === 'AES-128' || this.method === 'NONE') {
2724
2683
  return true;
2725
2684
  }
2726
2685
  if (this.keyFormat === 'identity') {
@@ -2742,13 +2701,14 @@ class LevelKey {
2742
2701
  if (!this.encrypted || !this.uri) {
2743
2702
  return null;
2744
2703
  }
2745
- if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
2704
+ if (this.method === 'AES-128' && this.uri && !this.iv) {
2746
2705
  if (typeof sn !== 'number') {
2747
2706
  // We are fetching decryption data for a initialization segment
2748
- // If the segment was encrypted with AES-128/256
2707
+ // If the segment was encrypted with AES-128
2749
2708
  // It must have an IV defined. We cannot substitute the Segment Number in.
2750
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2751
-
2709
+ if (this.method === 'AES-128' && !this.iv) {
2710
+ logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2711
+ }
2752
2712
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
2753
2713
  sn = 0;
2754
2714
  }
@@ -3027,28 +2987,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
3027
2987
  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
3028
2988
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
3029
2989
  }
2990
+
2991
+ // Idealy fLaC and Opus would be first (spec-compliant) but
2992
+ // some browsers will report that fLaC is supported then fail.
2993
+ // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3030
2994
  const codecsToCheck = {
3031
- // Idealy fLaC and Opus would be first (spec-compliant) but
3032
- // some browsers will report that fLaC is supported then fail.
3033
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3034
2995
  flac: ['flac', 'fLaC', 'FLAC'],
3035
- opus: ['opus', 'Opus'],
3036
- // Replace audio codec info if browser does not support mp4a.40.34,
3037
- // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
3038
- 'mp4a.40.34': ['mp3']
2996
+ opus: ['opus', 'Opus']
3039
2997
  }[lowerCaseCodec];
3040
2998
  for (let i = 0; i < codecsToCheck.length; i++) {
3041
- var _getMediaSource;
3042
2999
  if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
3043
3000
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
3044
3001
  return codecsToCheck[i];
3045
- } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
3046
- return '';
3047
3002
  }
3048
3003
  }
3049
3004
  return lowerCaseCodec;
3050
3005
  }
3051
- const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
3006
+ const AUDIO_CODEC_REGEXP = /flac|opus/i;
3052
3007
  function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
3053
3008
  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
3054
3009
  }
@@ -3071,16 +3026,6 @@ function convertAVC1ToAVCOTI(codec) {
3071
3026
  }
3072
3027
  return codec;
3073
3028
  }
3074
- function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
3075
- const MediaSource = getMediaSource(preferManagedMediaSource) || {
3076
- isTypeSupported: () => false
3077
- };
3078
- return {
3079
- mpeg: MediaSource.isTypeSupported('audio/mpeg'),
3080
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
3081
- ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
3082
- };
3083
- }
3084
3029
 
3085
3030
  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;
3086
3031
  const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -4747,47 +4692,7 @@ class LatencyController {
4747
4692
  this.currentTime = 0;
4748
4693
  this.stallCount = 0;
4749
4694
  this._latency = null;
4750
- this.onTimeupdate = () => {
4751
- const {
4752
- media,
4753
- levelDetails
4754
- } = this;
4755
- if (!media || !levelDetails) {
4756
- return;
4757
- }
4758
- this.currentTime = media.currentTime;
4759
- const latency = this.computeLatency();
4760
- if (latency === null) {
4761
- return;
4762
- }
4763
- this._latency = latency;
4764
-
4765
- // Adapt playbackRate to meet target latency in low-latency mode
4766
- const {
4767
- lowLatencyMode,
4768
- maxLiveSyncPlaybackRate
4769
- } = this.config;
4770
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4771
- return;
4772
- }
4773
- const targetLatency = this.targetLatency;
4774
- if (targetLatency === null) {
4775
- return;
4776
- }
4777
- const distanceFromTarget = latency - targetLatency;
4778
- // Only adjust playbackRate when within one target duration of targetLatency
4779
- // and more than one second from under-buffering.
4780
- // Playback further than one target duration from target can be considered DVR playback.
4781
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4782
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4783
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4784
- const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4785
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4786
- media.playbackRate = Math.min(max, Math.max(1, rate));
4787
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4788
- media.playbackRate = 1;
4789
- }
4790
- };
4695
+ this.timeupdateHandler = () => this.timeupdate();
4791
4696
  this.hls = hls;
4792
4697
  this.config = hls.config;
4793
4698
  this.registerListeners();
@@ -4879,7 +4784,7 @@ class LatencyController {
4879
4784
  this.onMediaDetaching();
4880
4785
  this.levelDetails = null;
4881
4786
  // @ts-ignore
4882
- this.hls = null;
4787
+ this.hls = this.timeupdateHandler = null;
4883
4788
  }
4884
4789
  registerListeners() {
4885
4790
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -4897,11 +4802,11 @@ class LatencyController {
4897
4802
  }
4898
4803
  onMediaAttached(event, data) {
4899
4804
  this.media = data.media;
4900
- this.media.addEventListener('timeupdate', this.onTimeupdate);
4805
+ this.media.addEventListener('timeupdate', this.timeupdateHandler);
4901
4806
  }
4902
4807
  onMediaDetaching() {
4903
4808
  if (this.media) {
4904
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4809
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4905
4810
  this.media = null;
4906
4811
  }
4907
4812
  }
@@ -4915,10 +4820,10 @@ class LatencyController {
4915
4820
  }) {
4916
4821
  this.levelDetails = details;
4917
4822
  if (details.advanced) {
4918
- this.onTimeupdate();
4823
+ this.timeupdate();
4919
4824
  }
4920
4825
  if (!details.live && this.media) {
4921
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4826
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4922
4827
  }
4923
4828
  }
4924
4829
  onError(event, data) {
@@ -4928,7 +4833,48 @@ class LatencyController {
4928
4833
  }
4929
4834
  this.stallCount++;
4930
4835
  if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
4931
- this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
4836
+ logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
4837
+ }
4838
+ }
4839
+ timeupdate() {
4840
+ const {
4841
+ media,
4842
+ levelDetails
4843
+ } = this;
4844
+ if (!media || !levelDetails) {
4845
+ return;
4846
+ }
4847
+ this.currentTime = media.currentTime;
4848
+ const latency = this.computeLatency();
4849
+ if (latency === null) {
4850
+ return;
4851
+ }
4852
+ this._latency = latency;
4853
+
4854
+ // Adapt playbackRate to meet target latency in low-latency mode
4855
+ const {
4856
+ lowLatencyMode,
4857
+ maxLiveSyncPlaybackRate
4858
+ } = this.config;
4859
+ if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4860
+ return;
4861
+ }
4862
+ const targetLatency = this.targetLatency;
4863
+ if (targetLatency === null) {
4864
+ return;
4865
+ }
4866
+ const distanceFromTarget = latency - targetLatency;
4867
+ // Only adjust playbackRate when within one target duration of targetLatency
4868
+ // and more than one second from under-buffering.
4869
+ // Playback further than one target duration from target can be considered DVR playback.
4870
+ const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4871
+ const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4872
+ if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4873
+ const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4874
+ const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4875
+ media.playbackRate = Math.min(max, Math.max(1, rate));
4876
+ } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4877
+ media.playbackRate = 1;
4932
4878
  }
4933
4879
  }
4934
4880
  estimateLiveEdge() {
@@ -5700,13 +5646,18 @@ var ErrorActionFlags = {
5700
5646
  MoveAllAlternatesMatchingHDCP: 2,
5701
5647
  SwitchToSDR: 4
5702
5648
  }; // Reserved for future use
5703
- class ErrorController extends Logger {
5649
+ class ErrorController {
5704
5650
  constructor(hls) {
5705
- super('error-controller', hls.logger);
5706
5651
  this.hls = void 0;
5707
5652
  this.playlistError = 0;
5708
5653
  this.penalizedRenditions = {};
5654
+ this.log = void 0;
5655
+ this.warn = void 0;
5656
+ this.error = void 0;
5709
5657
  this.hls = hls;
5658
+ this.log = logger.log.bind(logger, `[info]:`);
5659
+ this.warn = logger.warn.bind(logger, `[warning]:`);
5660
+ this.error = logger.error.bind(logger, `[error]:`);
5710
5661
  this.registerListeners();
5711
5662
  }
5712
5663
  registerListeners() {
@@ -6058,13 +6009,16 @@ class ErrorController extends Logger {
6058
6009
  }
6059
6010
  }
6060
6011
 
6061
- class BasePlaylistController extends Logger {
6012
+ class BasePlaylistController {
6062
6013
  constructor(hls, logPrefix) {
6063
- super(logPrefix, hls.logger);
6064
6014
  this.hls = void 0;
6065
6015
  this.timer = -1;
6066
6016
  this.requestScheduled = -1;
6067
6017
  this.canLoad = false;
6018
+ this.log = void 0;
6019
+ this.warn = void 0;
6020
+ this.log = logger.log.bind(logger, `${logPrefix}:`);
6021
+ this.warn = logger.warn.bind(logger, `${logPrefix}:`);
6068
6022
  this.hls = hls;
6069
6023
  }
6070
6024
  destroy() {
@@ -6097,7 +6051,7 @@ class BasePlaylistController extends Logger {
6097
6051
  try {
6098
6052
  uri = new self.URL(attr.URI, previous.url).href;
6099
6053
  } catch (error) {
6100
- this.warn(`Could not construct new URL for Rendition Report: ${error}`);
6054
+ logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
6101
6055
  uri = attr.URI || '';
6102
6056
  }
6103
6057
  // Use exact match. Otherwise, the last partial match, if any, will be used
@@ -6856,9 +6810,8 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
6856
6810
  return -1;
6857
6811
  }
6858
6812
 
6859
- class AbrController extends Logger {
6813
+ class AbrController {
6860
6814
  constructor(_hls) {
6861
- super('abr', _hls.logger);
6862
6815
  this.hls = void 0;
6863
6816
  this.lastLevelLoadSec = 0;
6864
6817
  this.lastLoadedFragLevel = -1;
@@ -6972,7 +6925,7 @@ class AbrController extends Logger {
6972
6925
  this.resetEstimator(nextLoadLevelBitrate);
6973
6926
  }
6974
6927
  this.clearTimer();
6975
- this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6928
+ logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6976
6929
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
6977
6930
  Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
6978
6931
  Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
@@ -6992,7 +6945,7 @@ class AbrController extends Logger {
6992
6945
  }
6993
6946
  resetEstimator(abrEwmaDefaultEstimate) {
6994
6947
  if (abrEwmaDefaultEstimate) {
6995
- this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6948
+ logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6996
6949
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
6997
6950
  }
6998
6951
  this.firstSelection = -1;
@@ -7224,7 +7177,7 @@ class AbrController extends Logger {
7224
7177
  }
7225
7178
  const firstLevel = this.hls.firstLevel;
7226
7179
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
7227
- this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7180
+ logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7228
7181
  return clamped;
7229
7182
  }
7230
7183
  get forcedAutoLevel() {
@@ -7309,13 +7262,13 @@ class AbrController extends Logger {
7309
7262
  // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
7310
7263
  const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
7311
7264
  maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
7312
- this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7265
+ logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7313
7266
  // don't use conservative factor on bitrate test
7314
7267
  bwFactor = bwUpFactor = 1;
7315
7268
  }
7316
7269
  }
7317
7270
  const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
7318
- this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7271
+ logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7319
7272
  if (bestLevel > -1) {
7320
7273
  return bestLevel;
7321
7274
  }
@@ -7377,7 +7330,7 @@ class AbrController extends Logger {
7377
7330
  currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
7378
7331
  currentFrameRate = minFramerate;
7379
7332
  currentBw = Math.max(currentBw, minBitrate);
7380
- this.log(`picked start tier ${JSON.stringify(startTier)}`);
7333
+ logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
7381
7334
  } else {
7382
7335
  currentCodecSet = level == null ? void 0 : level.codecSet;
7383
7336
  currentVideoRange = level == null ? void 0 : level.videoRange;
@@ -7386,7 +7339,7 @@ class AbrController extends Logger {
7386
7339
  const ttfbEstimateSec = this.bwEstimator.getEstimateTTFB() / 1000;
7387
7340
  const levelsSkipped = [];
7388
7341
  for (let i = maxAutoLevel; i >= minAutoLevel; i--) {
7389
- var _levelInfo$supportedR, _levelInfo$supportedR2;
7342
+ var _levelInfo$supportedR;
7390
7343
  const levelInfo = levels[i];
7391
7344
  const upSwitch = i > selectionBaseLevel;
7392
7345
  if (!levelInfo) {
@@ -7401,11 +7354,11 @@ class AbrController extends Logger {
7401
7354
  const levels = this.hls.levels;
7402
7355
  const index = levels.indexOf(levelInfo);
7403
7356
  if (decodingInfo.error) {
7404
- this.warn(`MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7357
+ logger.warn(`[abr] MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7405
7358
  } else if (!decodingInfo.supported) {
7406
- this.warn(`Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7359
+ logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7407
7360
  if (index > -1 && levels.length > 1) {
7408
- this.log(`Removing unsupported level ${index}`);
7361
+ logger.log(`[abr] Removing unsupported level ${index}`);
7409
7362
  this.hls.removeLevel(index);
7410
7363
  }
7411
7364
  }
@@ -7417,7 +7370,7 @@ class AbrController extends Logger {
7417
7370
 
7418
7371
  // skip candidates which change codec-family or video-range,
7419
7372
  // and which decrease or increase frame-rate for up and down-switch respectfully
7420
- if (currentCodecSet && levelInfo.codecSet !== currentCodecSet || currentVideoRange && levelInfo.videoRange !== currentVideoRange || upSwitch && currentFrameRate > levelInfo.frameRate || !upSwitch && currentFrameRate > 0 && currentFrameRate < levelInfo.frameRate || !((_levelInfo$supportedR = levelInfo.supportedResult) != null && (_levelInfo$supportedR2 = _levelInfo$supportedR.decodingInfoResults) != null && _levelInfo$supportedR2[0].smooth)) {
7373
+ if (currentCodecSet && levelInfo.codecSet !== currentCodecSet || currentVideoRange && levelInfo.videoRange !== currentVideoRange || upSwitch && currentFrameRate > levelInfo.frameRate || !upSwitch && currentFrameRate > 0 && currentFrameRate < levelInfo.frameRate || levelInfo.supportedResult && !((_levelInfo$supportedR = levelInfo.supportedResult.decodingInfoResults) != null && _levelInfo$supportedR[0].smooth)) {
7421
7374
  levelsSkipped.push(i);
7422
7375
  continue;
7423
7376
  }
@@ -7452,9 +7405,9 @@ class AbrController extends Logger {
7452
7405
  const forcedAutoLevel = this.forcedAutoLevel;
7453
7406
  if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
7454
7407
  if (levelsSkipped.length) {
7455
- 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}`);
7408
+ 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}`);
7456
7409
  }
7457
- 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}`);
7410
+ 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}`);
7458
7411
  }
7459
7412
  if (firstSelection) {
7460
7413
  this.firstSelection = i;
@@ -7504,9 +7457,8 @@ class AbrController extends Logger {
7504
7457
  * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
7505
7458
  * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
7506
7459
  */
7507
- class TaskLoop extends Logger {
7508
- constructor(label, logger) {
7509
- super(label, logger);
7460
+ class TaskLoop {
7461
+ constructor() {
7510
7462
  this._boundTick = void 0;
7511
7463
  this._tickTimer = null;
7512
7464
  this._tickInterval = null;
@@ -8597,8 +8549,8 @@ function createLoaderContext(frag, part = null) {
8597
8549
  var _frag$decryptdata;
8598
8550
  let byteRangeStart = start;
8599
8551
  let byteRangeEnd = end;
8600
- if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
8601
- // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
8552
+ if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
8553
+ // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
8602
8554
  // has the unencrypted size specified in the range.
8603
8555
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
8604
8556
  const fragmentLen = end - start;
@@ -8631,9 +8583,6 @@ function createGapLoadError(frag, part) {
8631
8583
  (part ? part : frag).stats.aborted = true;
8632
8584
  return new LoadError(errorData);
8633
8585
  }
8634
- function isMethodFullSegmentAesCbc(method) {
8635
- return method === 'AES-128' || method === 'AES-256';
8636
- }
8637
8586
  class LoadError extends Error {
8638
8587
  constructor(data) {
8639
8588
  super(data.error.message);
@@ -8643,61 +8592,33 @@ class LoadError extends Error {
8643
8592
  }
8644
8593
 
8645
8594
  class AESCrypto {
8646
- constructor(subtle, iv, aesMode) {
8595
+ constructor(subtle, iv) {
8647
8596
  this.subtle = void 0;
8648
8597
  this.aesIV = void 0;
8649
- this.aesMode = void 0;
8650
8598
  this.subtle = subtle;
8651
8599
  this.aesIV = iv;
8652
- this.aesMode = aesMode;
8653
8600
  }
8654
8601
  decrypt(data, key) {
8655
- switch (this.aesMode) {
8656
- case DecrypterAesMode.cbc:
8657
- return this.subtle.decrypt({
8658
- name: 'AES-CBC',
8659
- iv: this.aesIV
8660
- }, key, data);
8661
- case DecrypterAesMode.ctr:
8662
- return this.subtle.decrypt({
8663
- name: 'AES-CTR',
8664
- counter: this.aesIV,
8665
- length: 64
8666
- },
8667
- //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
8668
- key, data);
8669
- default:
8670
- throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
8671
- }
8602
+ return this.subtle.decrypt({
8603
+ name: 'AES-CBC',
8604
+ iv: this.aesIV
8605
+ }, key, data);
8672
8606
  }
8673
8607
  }
8674
8608
 
8675
8609
  class FastAESKey {
8676
- constructor(subtle, key, aesMode) {
8610
+ constructor(subtle, key) {
8677
8611
  this.subtle = void 0;
8678
8612
  this.key = void 0;
8679
- this.aesMode = void 0;
8680
8613
  this.subtle = subtle;
8681
8614
  this.key = key;
8682
- this.aesMode = aesMode;
8683
8615
  }
8684
8616
  expandKey() {
8685
- const subtleAlgoName = getSubtleAlgoName(this.aesMode);
8686
8617
  return this.subtle.importKey('raw', this.key, {
8687
- name: subtleAlgoName
8618
+ name: 'AES-CBC'
8688
8619
  }, false, ['encrypt', 'decrypt']);
8689
8620
  }
8690
8621
  }
8691
- function getSubtleAlgoName(aesMode) {
8692
- switch (aesMode) {
8693
- case DecrypterAesMode.cbc:
8694
- return 'AES-CBC';
8695
- case DecrypterAesMode.ctr:
8696
- return 'AES-CTR';
8697
- default:
8698
- throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
8699
- }
8700
- }
8701
8622
 
8702
8623
  // PKCS7
8703
8624
  function removePadding(array) {
@@ -8947,8 +8868,7 @@ class Decrypter {
8947
8868
  this.currentIV = null;
8948
8869
  this.currentResult = null;
8949
8870
  this.useSoftware = void 0;
8950
- this.enableSoftwareAES = void 0;
8951
- this.enableSoftwareAES = config.enableSoftwareAES;
8871
+ this.useSoftware = config.enableSoftwareAES;
8952
8872
  this.removePKCS7Padding = removePKCS7Padding;
8953
8873
  // built in decryptor expects PKCS7 padding
8954
8874
  if (removePKCS7Padding) {
@@ -8961,7 +8881,9 @@ class Decrypter {
8961
8881
  /* no-op */
8962
8882
  }
8963
8883
  }
8964
- this.useSoftware = this.subtle === null;
8884
+ if (this.subtle === null) {
8885
+ this.useSoftware = true;
8886
+ }
8965
8887
  }
8966
8888
  destroy() {
8967
8889
  this.subtle = null;
@@ -8999,10 +8921,10 @@ class Decrypter {
8999
8921
  this.softwareDecrypter = null;
9000
8922
  }
9001
8923
  }
9002
- decrypt(data, key, iv, aesMode) {
8924
+ decrypt(data, key, iv) {
9003
8925
  if (this.useSoftware) {
9004
8926
  return new Promise((resolve, reject) => {
9005
- this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
8927
+ this.softwareDecrypt(new Uint8Array(data), key, iv);
9006
8928
  const decryptResult = this.flush();
9007
8929
  if (decryptResult) {
9008
8930
  resolve(decryptResult.buffer);
@@ -9011,21 +8933,17 @@ class Decrypter {
9011
8933
  }
9012
8934
  });
9013
8935
  }
9014
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
8936
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
9015
8937
  }
9016
8938
 
9017
8939
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
9018
8940
  // data is handled in the flush() call
9019
- softwareDecrypt(data, key, iv, aesMode) {
8941
+ softwareDecrypt(data, key, iv) {
9020
8942
  const {
9021
8943
  currentIV,
9022
8944
  currentResult,
9023
8945
  remainderData
9024
8946
  } = this;
9025
- if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
9026
- logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
9027
- return null;
9028
- }
9029
8947
  this.logOnce('JS AES decrypt');
9030
8948
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
9031
8949
  // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
@@ -9058,11 +8976,11 @@ class Decrypter {
9058
8976
  }
9059
8977
  return result;
9060
8978
  }
9061
- webCryptoDecrypt(data, key, iv, aesMode) {
8979
+ webCryptoDecrypt(data, key, iv) {
9062
8980
  const subtle = this.subtle;
9063
8981
  if (this.key !== key || !this.fastAesKey) {
9064
8982
  this.key = key;
9065
- this.fastAesKey = new FastAESKey(subtle, key, aesMode);
8983
+ this.fastAesKey = new FastAESKey(subtle, key);
9066
8984
  }
9067
8985
  return this.fastAesKey.expandKey().then(aesKey => {
9068
8986
  // decrypt using web crypto
@@ -9070,25 +8988,22 @@ class Decrypter {
9070
8988
  return Promise.reject(new Error('web crypto not initialized'));
9071
8989
  }
9072
8990
  this.logOnce('WebCrypto AES decrypt');
9073
- const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
8991
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv));
9074
8992
  return crypto.decrypt(data.buffer, aesKey);
9075
8993
  }).catch(err => {
9076
8994
  logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
9077
- return this.onWebCryptoError(data, key, iv, aesMode);
8995
+ return this.onWebCryptoError(data, key, iv);
9078
8996
  });
9079
8997
  }
9080
- onWebCryptoError(data, key, iv, aesMode) {
9081
- const enableSoftwareAES = this.enableSoftwareAES;
9082
- if (enableSoftwareAES) {
9083
- this.useSoftware = true;
9084
- this.logEnabled = true;
9085
- this.softwareDecrypt(data, key, iv, aesMode);
9086
- const decryptResult = this.flush();
9087
- if (decryptResult) {
9088
- return decryptResult.buffer;
9089
- }
8998
+ onWebCryptoError(data, key, iv) {
8999
+ this.useSoftware = true;
9000
+ this.logEnabled = true;
9001
+ this.softwareDecrypt(data, key, iv);
9002
+ const decryptResult = this.flush();
9003
+ if (decryptResult) {
9004
+ return decryptResult.buffer;
9090
9005
  }
9091
- throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
9006
+ throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
9092
9007
  }
9093
9008
  getValidChunk(data) {
9094
9009
  let currentChunk = data;
@@ -9139,7 +9054,7 @@ const State = {
9139
9054
  };
9140
9055
  class BaseStreamController extends TaskLoop {
9141
9056
  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
9142
- super(logPrefix, hls.logger);
9057
+ super();
9143
9058
  this.hls = void 0;
9144
9059
  this.fragPrevious = null;
9145
9060
  this.fragCurrent = null;
@@ -9164,88 +9079,22 @@ class BaseStreamController extends TaskLoop {
9164
9079
  this.startFragRequested = false;
9165
9080
  this.decrypter = void 0;
9166
9081
  this.initPTS = [];
9167
- this.onMediaSeeking = () => {
9168
- const {
9169
- config,
9170
- fragCurrent,
9171
- media,
9172
- mediaBuffer,
9173
- state
9174
- } = this;
9175
- const currentTime = media ? media.currentTime : 0;
9176
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9177
- this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9178
- if (this.state === State.ENDED) {
9179
- this.resetLoadingState();
9180
- } else if (fragCurrent) {
9181
- // Seeking while frag load is in progress
9182
- const tolerance = config.maxFragLookUpTolerance;
9183
- const fragStartOffset = fragCurrent.start - tolerance;
9184
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9185
- // if seeking out of buffered range or into new one
9186
- if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9187
- const pastFragment = currentTime > fragEndOffset;
9188
- // if the seek position is outside the current fragment range
9189
- if (currentTime < fragStartOffset || pastFragment) {
9190
- if (pastFragment && fragCurrent.loader) {
9191
- this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9192
- fragCurrent.abortRequests();
9193
- this.resetLoadingState();
9194
- }
9195
- this.fragPrevious = null;
9196
- }
9197
- }
9198
- }
9199
- if (media) {
9200
- // Remove gap fragments
9201
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9202
- this.lastCurrentTime = currentTime;
9203
- }
9204
-
9205
- // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9206
- if (!this.loadedmetadata && !bufferInfo.len) {
9207
- this.nextLoadPosition = this.startPosition = currentTime;
9208
- }
9209
-
9210
- // Async tick to speed up processing
9211
- this.tickImmediate();
9212
- };
9213
- this.onMediaEnded = () => {
9214
- // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9215
- this.startPosition = this.lastCurrentTime = 0;
9216
- if (this.playlistType === PlaylistLevelType.MAIN) {
9217
- this.hls.trigger(Events.MEDIA_ENDED, {
9218
- stalled: false
9219
- });
9220
- }
9221
- };
9082
+ this.onvseeking = null;
9083
+ this.onvended = null;
9084
+ this.logPrefix = '';
9085
+ this.log = void 0;
9086
+ this.warn = void 0;
9222
9087
  this.playlistType = playlistType;
9088
+ this.logPrefix = logPrefix;
9089
+ this.log = logger.log.bind(logger, `${logPrefix}:`);
9090
+ this.warn = logger.warn.bind(logger, `${logPrefix}:`);
9223
9091
  this.hls = hls;
9224
9092
  this.fragmentLoader = new FragmentLoader(hls.config);
9225
9093
  this.keyLoader = keyLoader;
9226
9094
  this.fragmentTracker = fragmentTracker;
9227
9095
  this.config = hls.config;
9228
9096
  this.decrypter = new Decrypter(hls.config);
9229
- }
9230
- registerListeners() {
9231
- const {
9232
- hls
9233
- } = this;
9234
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
9235
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
9236
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
9237
9097
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9238
- hls.on(Events.ERROR, this.onError, this);
9239
- }
9240
- unregisterListeners() {
9241
- const {
9242
- hls
9243
- } = this;
9244
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
9245
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
9246
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
9247
- hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9248
- hls.off(Events.ERROR, this.onError, this);
9249
9098
  }
9250
9099
  doTick() {
9251
9100
  this.onTickEnd();
@@ -9299,8 +9148,10 @@ class BaseStreamController extends TaskLoop {
9299
9148
  }
9300
9149
  onMediaAttached(event, data) {
9301
9150
  const media = this.media = this.mediaBuffer = data.media;
9302
- media.addEventListener('seeking', this.onMediaSeeking);
9303
- media.addEventListener('ended', this.onMediaEnded);
9151
+ this.onvseeking = this.onMediaSeeking.bind(this);
9152
+ this.onvended = this.onMediaEnded.bind(this);
9153
+ media.addEventListener('seeking', this.onvseeking);
9154
+ media.addEventListener('ended', this.onvended);
9304
9155
  const config = this.config;
9305
9156
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
9306
9157
  this.startLoad(config.startPosition);
@@ -9314,9 +9165,10 @@ class BaseStreamController extends TaskLoop {
9314
9165
  }
9315
9166
 
9316
9167
  // remove video listeners
9317
- if (media) {
9318
- media.removeEventListener('seeking', this.onMediaSeeking);
9319
- media.removeEventListener('ended', this.onMediaEnded);
9168
+ if (media && this.onvseeking && this.onvended) {
9169
+ media.removeEventListener('seeking', this.onvseeking);
9170
+ media.removeEventListener('ended', this.onvended);
9171
+ this.onvseeking = this.onvended = null;
9320
9172
  }
9321
9173
  if (this.keyLoader) {
9322
9174
  this.keyLoader.detach();
@@ -9326,8 +9178,56 @@ class BaseStreamController extends TaskLoop {
9326
9178
  this.fragmentTracker.removeAllFragments();
9327
9179
  this.stopLoad();
9328
9180
  }
9329
- onManifestLoading() {}
9330
- onError(event, data) {}
9181
+ onMediaSeeking() {
9182
+ const {
9183
+ config,
9184
+ fragCurrent,
9185
+ media,
9186
+ mediaBuffer,
9187
+ state
9188
+ } = this;
9189
+ const currentTime = media ? media.currentTime : 0;
9190
+ const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9191
+ this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9192
+ if (this.state === State.ENDED) {
9193
+ this.resetLoadingState();
9194
+ } else if (fragCurrent) {
9195
+ // Seeking while frag load is in progress
9196
+ const tolerance = config.maxFragLookUpTolerance;
9197
+ const fragStartOffset = fragCurrent.start - tolerance;
9198
+ const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9199
+ // if seeking out of buffered range or into new one
9200
+ if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9201
+ const pastFragment = currentTime > fragEndOffset;
9202
+ // if the seek position is outside the current fragment range
9203
+ if (currentTime < fragStartOffset || pastFragment) {
9204
+ if (pastFragment && fragCurrent.loader) {
9205
+ this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9206
+ fragCurrent.abortRequests();
9207
+ this.resetLoadingState();
9208
+ }
9209
+ this.fragPrevious = null;
9210
+ }
9211
+ }
9212
+ }
9213
+ if (media) {
9214
+ // Remove gap fragments
9215
+ this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9216
+ this.lastCurrentTime = currentTime;
9217
+ }
9218
+
9219
+ // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9220
+ if (!this.loadedmetadata && !bufferInfo.len) {
9221
+ this.nextLoadPosition = this.startPosition = currentTime;
9222
+ }
9223
+
9224
+ // Async tick to speed up processing
9225
+ this.tickImmediate();
9226
+ }
9227
+ onMediaEnded() {
9228
+ // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9229
+ this.startPosition = this.lastCurrentTime = 0;
9230
+ }
9331
9231
  onManifestLoaded(event, data) {
9332
9232
  this.startTimeOffset = data.startTimeOffset;
9333
9233
  this.initPTS = [];
@@ -9337,7 +9237,7 @@ class BaseStreamController extends TaskLoop {
9337
9237
  this.stopLoad();
9338
9238
  super.onHandlerDestroying();
9339
9239
  // @ts-ignore
9340
- this.hls = this.onMediaSeeking = this.onMediaEnded = null;
9240
+ this.hls = null;
9341
9241
  }
9342
9242
  onHandlerDestroyed() {
9343
9243
  this.state = State.STOPPED;
@@ -9468,10 +9368,10 @@ class BaseStreamController extends TaskLoop {
9468
9368
  const decryptData = frag.decryptdata;
9469
9369
 
9470
9370
  // check to see if the payload needs to be decrypted
9471
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
9371
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
9472
9372
  const startTime = self.performance.now();
9473
9373
  // decrypt init segment data
9474
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
9374
+ return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
9475
9375
  hls.trigger(Events.ERROR, {
9476
9376
  type: ErrorTypes.MEDIA_ERROR,
9477
9377
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -9583,7 +9483,7 @@ class BaseStreamController extends TaskLoop {
9583
9483
  }
9584
9484
  let keyLoadingPromise = null;
9585
9485
  if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
9586
- this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
9486
+ this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
9587
9487
  this.state = State.KEY_LOADING;
9588
9488
  this.fragCurrent = frag;
9589
9489
  keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
@@ -9614,7 +9514,7 @@ class BaseStreamController extends TaskLoop {
9614
9514
  const partIndex = this.getNextPart(partList, frag, targetBufferTime);
9615
9515
  if (partIndex > -1) {
9616
9516
  const part = partList[partIndex];
9617
- 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))}`);
9517
+ 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))}`);
9618
9518
  this.nextLoadPosition = part.start + part.duration;
9619
9519
  this.state = State.FRAG_LOADING;
9620
9520
  let _result;
@@ -9643,7 +9543,7 @@ class BaseStreamController extends TaskLoop {
9643
9543
  }
9644
9544
  }
9645
9545
  }
9646
- 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))}`);
9546
+ 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))}`);
9647
9547
  // Don't update nextLoadPosition for fragments which are not buffered
9648
9548
  if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
9649
9549
  this.nextLoadPosition = frag.start + frag.duration;
@@ -10228,7 +10128,7 @@ class BaseStreamController extends TaskLoop {
10228
10128
  errorAction.resolved = true;
10229
10129
  }
10230
10130
  } else {
10231
- this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10131
+ logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10232
10132
  return;
10233
10133
  }
10234
10134
  } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
@@ -10637,7 +10537,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
10637
10537
  */
10638
10538
  function getAudioConfig(observer, data, offset, audioCodec) {
10639
10539
  let adtsObjectType;
10640
- let originalAdtsObjectType;
10641
10540
  let adtsExtensionSamplingIndex;
10642
10541
  let adtsChannelConfig;
10643
10542
  let config;
@@ -10645,7 +10544,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10645
10544
  const manifestCodec = audioCodec;
10646
10545
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
10647
10546
  // byte 2
10648
- adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10547
+ adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10649
10548
  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
10650
10549
  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
10651
10550
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
@@ -10662,8 +10561,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10662
10561
  // byte 3
10663
10562
  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
10664
10563
  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
10665
- // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
10666
- if (/firefox|palemoon/i.test(userAgent)) {
10564
+ // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
10565
+ if (/firefox/i.test(userAgent)) {
10667
10566
  if (adtsSamplingIndex >= 6) {
10668
10567
  adtsObjectType = 5;
10669
10568
  config = new Array(4);
@@ -10757,7 +10656,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10757
10656
  samplerate: adtsSamplingRates[adtsSamplingIndex],
10758
10657
  channelCount: adtsChannelConfig,
10759
10658
  codec: 'mp4a.40.' + adtsObjectType,
10760
- parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
10761
10659
  manifestCodec
10762
10660
  };
10763
10661
  }
@@ -10812,8 +10710,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
10812
10710
  track.channelCount = config.channelCount;
10813
10711
  track.codec = config.codec;
10814
10712
  track.manifestCodec = config.manifestCodec;
10815
- track.parsedCodec = config.parsedCodec;
10816
- logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10713
+ logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10817
10714
  }
10818
10715
  }
10819
10716
  function getFrameDuration(samplerate) {
@@ -11989,7 +11886,7 @@ class SampleAesDecrypter {
11989
11886
  });
11990
11887
  }
11991
11888
  decryptBuffer(encryptedData) {
11992
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
11889
+ return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
11993
11890
  }
11994
11891
 
11995
11892
  // AAC - encrypt all full 16 bytes blocks starting from offset 16
@@ -13947,24 +13844,12 @@ class MP4Remuxer {
13947
13844
  inputSamples[0].dts = firstDTS;
13948
13845
  inputSamples[0].pts = firstPTS;
13949
13846
  } else {
13950
- let isPTSOrderRetained = true;
13951
13847
  for (let i = 0; i < inputSamples.length; i++) {
13952
- if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
13848
+ if (inputSamples[i].dts > firstPTS) {
13953
13849
  break;
13954
13850
  }
13955
- const prevPTS = inputSamples[i].pts;
13956
13851
  inputSamples[i].dts -= delta;
13957
13852
  inputSamples[i].pts -= delta;
13958
-
13959
- // check to see if this sample's PTS order has changed
13960
- // relative to the next one
13961
- if (i < inputSamples.length - 1) {
13962
- const nextSamplePTS = inputSamples[i + 1].pts;
13963
- const currentSamplePTS = inputSamples[i].pts;
13964
- const currentOrder = nextSamplePTS <= currentSamplePTS;
13965
- const prevOrder = nextSamplePTS <= prevPTS;
13966
- isPTSOrderRetained = currentOrder == prevOrder;
13967
- }
13968
13853
  }
13969
13854
  }
13970
13855
  logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
@@ -14245,7 +14130,7 @@ class MP4Remuxer {
14245
14130
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
14246
14131
  for (let j = 0; j < missing; j++) {
14247
14132
  const newStamp = Math.max(nextPts, 0);
14248
- let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14133
+ let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
14249
14134
  if (!fillFrame) {
14250
14135
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
14251
14136
  fillFrame = sample.unit.subarray();
@@ -14373,7 +14258,7 @@ class MP4Remuxer {
14373
14258
  // samples count of this segment's duration
14374
14259
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
14375
14260
  // silent frame
14376
- const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14261
+ const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
14377
14262
  logger.warn('[mp4-remuxer]: remux empty Audio');
14378
14263
  // Can't remux if we can't generate a silent frame...
14379
14264
  if (!silentFrame) {
@@ -14767,15 +14652,13 @@ class Transmuxer {
14767
14652
  initSegmentData
14768
14653
  } = transmuxConfig;
14769
14654
  const keyData = getEncryptionType(uintData, decryptdata);
14770
- if (keyData && isFullSegmentEncryption(keyData.method)) {
14655
+ if (keyData && keyData.method === 'AES-128') {
14771
14656
  const decrypter = this.getDecrypter();
14772
- const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
14773
-
14774
14657
  // Software decryption is synchronous; webCrypto is not
14775
14658
  if (decrypter.isSync()) {
14776
14659
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
14777
14660
  // data is handled in the flush() call
14778
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
14661
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
14779
14662
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
14780
14663
  const loadingParts = chunkMeta.part > -1;
14781
14664
  if (loadingParts) {
@@ -14787,7 +14670,7 @@ class Transmuxer {
14787
14670
  }
14788
14671
  uintData = new Uint8Array(decryptedData);
14789
14672
  } else {
14790
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
14673
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
14791
14674
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
14792
14675
  // the decrypted data has been transmuxed
14793
14676
  const result = this.push(decryptedData, null, chunkMeta);
@@ -15441,7 +15324,14 @@ class TransmuxerInterface {
15441
15324
  this.observer = new EventEmitter();
15442
15325
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
15443
15326
  this.observer.on(Events.ERROR, forwardMessage);
15444
- const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
15327
+ const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
15328
+ isTypeSupported: () => false
15329
+ };
15330
+ const m2tsTypeSupported = {
15331
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
15332
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
15333
+ ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
15334
+ };
15445
15335
 
15446
15336
  // navigator.vendor is not always available in Web Worker
15447
15337
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -15729,7 +15619,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
15729
15619
 
15730
15620
  class AudioStreamController extends BaseStreamController {
15731
15621
  constructor(hls, fragmentTracker, keyLoader) {
15732
- super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
15622
+ super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);
15733
15623
  this.videoBuffer = null;
15734
15624
  this.videoTrackCC = -1;
15735
15625
  this.waitingVideoCC = -1;
@@ -15741,24 +15631,27 @@ class AudioStreamController extends BaseStreamController {
15741
15631
  this.flushing = false;
15742
15632
  this.bufferFlushed = false;
15743
15633
  this.cachedTrackLoadedData = null;
15744
- this.registerListeners();
15634
+ this._registerListeners();
15745
15635
  }
15746
15636
  onHandlerDestroying() {
15747
- this.unregisterListeners();
15637
+ this._unregisterListeners();
15748
15638
  super.onHandlerDestroying();
15749
15639
  this.mainDetails = null;
15750
15640
  this.bufferedTrack = null;
15751
15641
  this.switchingTrack = null;
15752
15642
  }
15753
- registerListeners() {
15754
- super.registerListeners();
15643
+ _registerListeners() {
15755
15644
  const {
15756
15645
  hls
15757
15646
  } = this;
15647
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
15648
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
15649
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
15758
15650
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
15759
15651
  hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
15760
15652
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
15761
15653
  hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15654
+ hls.on(Events.ERROR, this.onError, this);
15762
15655
  hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
15763
15656
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
15764
15657
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -15766,18 +15659,18 @@ class AudioStreamController extends BaseStreamController {
15766
15659
  hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
15767
15660
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
15768
15661
  }
15769
- unregisterListeners() {
15662
+ _unregisterListeners() {
15770
15663
  const {
15771
15664
  hls
15772
15665
  } = this;
15773
- if (!hls) {
15774
- return;
15775
- }
15776
- super.unregisterListeners();
15666
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
15667
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
15668
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
15777
15669
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
15778
15670
  hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
15779
15671
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
15780
15672
  hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15673
+ hls.off(Events.ERROR, this.onError, this);
15781
15674
  hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
15782
15675
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
15783
15676
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -16509,7 +16402,7 @@ class AudioStreamController extends BaseStreamController {
16509
16402
 
16510
16403
  class AudioTrackController extends BasePlaylistController {
16511
16404
  constructor(hls) {
16512
- super(hls, 'audio-track-controller');
16405
+ super(hls, '[audio-track-controller]');
16513
16406
  this.tracks = [];
16514
16407
  this.groupIds = null;
16515
16408
  this.tracksInGroup = [];
@@ -16828,23 +16721,26 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
16828
16721
 
16829
16722
  class SubtitleStreamController extends BaseStreamController {
16830
16723
  constructor(hls, fragmentTracker, keyLoader) {
16831
- super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
16724
+ super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);
16832
16725
  this.currentTrackId = -1;
16833
16726
  this.tracksBuffered = [];
16834
16727
  this.mainDetails = null;
16835
- this.registerListeners();
16728
+ this._registerListeners();
16836
16729
  }
16837
16730
  onHandlerDestroying() {
16838
- this.unregisterListeners();
16731
+ this._unregisterListeners();
16839
16732
  super.onHandlerDestroying();
16840
16733
  this.mainDetails = null;
16841
16734
  }
16842
- registerListeners() {
16843
- super.registerListeners();
16735
+ _registerListeners() {
16844
16736
  const {
16845
16737
  hls
16846
16738
  } = this;
16739
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
16740
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
16741
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16847
16742
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16743
+ hls.on(Events.ERROR, this.onError, this);
16848
16744
  hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
16849
16745
  hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
16850
16746
  hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -16852,12 +16748,15 @@ class SubtitleStreamController extends BaseStreamController {
16852
16748
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
16853
16749
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
16854
16750
  }
16855
- unregisterListeners() {
16856
- super.unregisterListeners();
16751
+ _unregisterListeners() {
16857
16752
  const {
16858
16753
  hls
16859
16754
  } = this;
16755
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
16756
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
16757
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16860
16758
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16759
+ hls.off(Events.ERROR, this.onError, this);
16861
16760
  hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
16862
16761
  hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
16863
16762
  hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -17084,10 +16983,10 @@ class SubtitleStreamController extends BaseStreamController {
17084
16983
  return;
17085
16984
  }
17086
16985
  // check to see if the payload needs to be decrypted
17087
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
16986
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
17088
16987
  const startTime = performance.now();
17089
16988
  // decrypt the subtitles
17090
- this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
16989
+ this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
17091
16990
  hls.trigger(Events.ERROR, {
17092
16991
  type: ErrorTypes.MEDIA_ERROR,
17093
16992
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -17221,7 +17120,7 @@ class BufferableInstance {
17221
17120
 
17222
17121
  class SubtitleTrackController extends BasePlaylistController {
17223
17122
  constructor(hls) {
17224
- super(hls, 'subtitle-track-controller');
17123
+ super(hls, '[subtitle-track-controller]');
17225
17124
  this.media = null;
17226
17125
  this.tracks = [];
17227
17126
  this.groupIds = null;
@@ -17230,10 +17129,10 @@ class SubtitleTrackController extends BasePlaylistController {
17230
17129
  this.currentTrack = null;
17231
17130
  this.selectDefaultTrack = true;
17232
17131
  this.queuedDefaultTrack = -1;
17132
+ this.asyncPollTrackChange = () => this.pollTrackChange(0);
17233
17133
  this.useTextTrackPolling = false;
17234
17134
  this.subtitlePollingInterval = -1;
17235
17135
  this._subtitleDisplay = true;
17236
- this.asyncPollTrackChange = () => this.pollTrackChange(0);
17237
17136
  this.onTextTracksChanged = () => {
17238
17137
  if (!this.useTextTrackPolling) {
17239
17138
  self.clearInterval(this.subtitlePollingInterval);
@@ -17267,7 +17166,6 @@ class SubtitleTrackController extends BasePlaylistController {
17267
17166
  this.tracks.length = 0;
17268
17167
  this.tracksInGroup.length = 0;
17269
17168
  this.currentTrack = null;
17270
- // @ts-ignore
17271
17169
  this.onTextTracksChanged = this.asyncPollTrackChange = null;
17272
17170
  super.destroy();
17273
17171
  }
@@ -17728,9 +17626,8 @@ class BufferOperationQueue {
17728
17626
  }
17729
17627
 
17730
17628
  const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
17731
- class BufferController extends Logger {
17629
+ class BufferController {
17732
17630
  constructor(hls) {
17733
- super('buffer-controller', hls.logger);
17734
17631
  // The level details used to determine duration, target-duration and live
17735
17632
  this.details = null;
17736
17633
  // cache the self generated object url to detect hijack of video tag
@@ -17760,6 +17657,9 @@ class BufferController extends Logger {
17760
17657
  this.tracks = {};
17761
17658
  this.pendingTracks = {};
17762
17659
  this.sourceBuffer = void 0;
17660
+ this.log = void 0;
17661
+ this.warn = void 0;
17662
+ this.error = void 0;
17763
17663
  this._onEndStreaming = event => {
17764
17664
  if (!this.hls) {
17765
17665
  return;
@@ -17805,11 +17705,15 @@ class BufferController extends Logger {
17805
17705
  _objectUrl
17806
17706
  } = this;
17807
17707
  if (mediaSrc !== _objectUrl) {
17808
- this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
17708
+ logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
17809
17709
  }
17810
17710
  };
17811
17711
  this.hls = hls;
17712
+ const logPrefix = '[buffer-controller]';
17812
17713
  this.appendSource = hls.config.preferManagedMediaSource;
17714
+ this.log = logger.log.bind(logger, logPrefix);
17715
+ this.warn = logger.warn.bind(logger, logPrefix);
17716
+ this.error = logger.error.bind(logger, logPrefix);
17813
17717
  this._initSourceBuffer();
17814
17718
  this.registerListeners();
17815
17719
  }
@@ -17822,12 +17726,6 @@ class BufferController extends Logger {
17822
17726
  this.lastMpegAudioChunk = null;
17823
17727
  // @ts-ignore
17824
17728
  this.hls = null;
17825
- // @ts-ignore
17826
- this._onMediaSourceOpen = this._onMediaSourceClose = null;
17827
- // @ts-ignore
17828
- this._onMediaSourceEnded = null;
17829
- // @ts-ignore
17830
- this._onStartStreaming = this._onEndStreaming = null;
17831
17729
  }
17832
17730
  registerListeners() {
17833
17731
  const {
@@ -21092,12 +20990,14 @@ class TimelineController {
21092
20990
  this.cea608Parser1 = this.cea608Parser2 = undefined;
21093
20991
  }
21094
20992
  initCea608Parsers() {
21095
- const channel1 = new OutputFilter(this, 'textTrack1');
21096
- const channel2 = new OutputFilter(this, 'textTrack2');
21097
- const channel3 = new OutputFilter(this, 'textTrack3');
21098
- const channel4 = new OutputFilter(this, 'textTrack4');
21099
- this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
21100
- this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
20993
+ if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) {
20994
+ const channel1 = new OutputFilter(this, 'textTrack1');
20995
+ const channel2 = new OutputFilter(this, 'textTrack2');
20996
+ const channel3 = new OutputFilter(this, 'textTrack3');
20997
+ const channel4 = new OutputFilter(this, 'textTrack4');
20998
+ this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
20999
+ this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
21000
+ }
21101
21001
  }
21102
21002
  addCues(trackName, startTime, endTime, screen, cueRanges) {
21103
21003
  // skip cues which overlap more than 50% with previously parsed time ranges
@@ -21335,7 +21235,7 @@ class TimelineController {
21335
21235
  if (inUseTracks != null && inUseTracks.length) {
21336
21236
  const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
21337
21237
  if (unusedTextTracks.length) {
21338
- this.hls.logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
21238
+ logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
21339
21239
  }
21340
21240
  }
21341
21241
  } else if (this.tracks.length) {
@@ -21380,23 +21280,26 @@ class TimelineController {
21380
21280
  return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
21381
21281
  }
21382
21282
  onFragLoading(event, data) {
21283
+ this.initCea608Parsers();
21284
+ const {
21285
+ cea608Parser1,
21286
+ cea608Parser2,
21287
+ lastCc,
21288
+ lastSn,
21289
+ lastPartIndex
21290
+ } = this;
21291
+ if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
21292
+ return;
21293
+ }
21383
21294
  // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
21384
- if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
21295
+ if (data.frag.type === PlaylistLevelType.MAIN) {
21385
21296
  var _data$part$index, _data$part;
21386
- const {
21387
- cea608Parser1,
21388
- cea608Parser2,
21389
- lastSn
21390
- } = this;
21391
- if (!cea608Parser1 || !cea608Parser2) {
21392
- return;
21393
- }
21394
21297
  const {
21395
21298
  cc,
21396
21299
  sn
21397
21300
  } = data.frag;
21398
- const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
21399
- if (!(sn === lastSn + 1 || sn === lastSn && partIndex === this.lastPartIndex + 1 || cc === this.lastCc)) {
21301
+ const partIndex = (_data$part$index = data == null ? void 0 : (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
21302
+ if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
21400
21303
  cea608Parser1.reset();
21401
21304
  cea608Parser2.reset();
21402
21305
  }
@@ -21453,7 +21356,7 @@ class TimelineController {
21453
21356
  frag: frag
21454
21357
  });
21455
21358
  }, error => {
21456
- hls.logger.log(`Failed to parse IMSC1: ${error}`);
21359
+ logger.log(`Failed to parse IMSC1: ${error}`);
21457
21360
  hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
21458
21361
  success: false,
21459
21362
  frag: frag,
@@ -21494,7 +21397,7 @@ class TimelineController {
21494
21397
  this._fallbackToIMSC1(frag, payload);
21495
21398
  }
21496
21399
  // Something went wrong while parsing. Trigger event with success false.
21497
- hls.logger.log(`Failed to parse VTT cue: ${error}`);
21400
+ logger.log(`Failed to parse VTT cue: ${error}`);
21498
21401
  if (missingInitPTS && maxAvCC > frag.cc) {
21499
21402
  return;
21500
21403
  }
@@ -21555,7 +21458,12 @@ class TimelineController {
21555
21458
  this.captionsTracks = {};
21556
21459
  }
21557
21460
  onFragParsingUserdata(event, data) {
21558
- if (!this.enabled || !this.config.enableCEA708Captions) {
21461
+ this.initCea608Parsers();
21462
+ const {
21463
+ cea608Parser1,
21464
+ cea608Parser2
21465
+ } = this;
21466
+ if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
21559
21467
  return;
21560
21468
  }
21561
21469
  const {
@@ -21570,12 +21478,9 @@ class TimelineController {
21570
21478
  for (let i = 0; i < samples.length; i++) {
21571
21479
  const ccBytes = samples[i].bytes;
21572
21480
  if (ccBytes) {
21573
- if (!this.cea608Parser1) {
21574
- this.initCea608Parsers();
21575
- }
21576
21481
  const ccdatas = this.extractCea608Data(ccBytes);
21577
- this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
21578
- this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
21482
+ cea608Parser1.addData(samples[i].pts, ccdatas[0]);
21483
+ cea608Parser2.addData(samples[i].pts, ccdatas[1]);
21579
21484
  }
21580
21485
  }
21581
21486
  }
@@ -21771,7 +21676,7 @@ class CapLevelController {
21771
21676
  const hls = this.hls;
21772
21677
  const maxLevel = this.getMaxLevel(levels.length - 1);
21773
21678
  if (maxLevel !== this.autoLevelCapping) {
21774
- hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
21679
+ logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
21775
21680
  }
21776
21681
  hls.autoLevelCapping = maxLevel;
21777
21682
  if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
@@ -21949,10 +21854,10 @@ class FPSController {
21949
21854
  totalDroppedFrames: droppedFrames
21950
21855
  });
21951
21856
  if (droppedFPS > 0) {
21952
- // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
21857
+ // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
21953
21858
  if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
21954
21859
  let currentLevel = hls.currentLevel;
21955
- hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
21860
+ logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
21956
21861
  if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
21957
21862
  currentLevel = currentLevel - 1;
21958
21863
  hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
@@ -21984,6 +21889,7 @@ class FPSController {
21984
21889
  }
21985
21890
  }
21986
21891
 
21892
+ const LOGGER_PREFIX = '[eme]';
21987
21893
  /**
21988
21894
  * Controller to deal with encrypted media extensions (EME)
21989
21895
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
@@ -21991,9 +21897,8 @@ class FPSController {
21991
21897
  * @class
21992
21898
  * @constructor
21993
21899
  */
21994
- class EMEController extends Logger {
21900
+ class EMEController {
21995
21901
  constructor(hls) {
21996
- super('eme', hls.logger);
21997
21902
  this.hls = void 0;
21998
21903
  this.config = void 0;
21999
21904
  this.media = null;
@@ -22003,100 +21908,12 @@ class EMEController extends Logger {
22003
21908
  this.mediaKeySessions = [];
22004
21909
  this.keyIdToKeySessionPromise = {};
22005
21910
  this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
22006
- this.onMediaEncrypted = event => {
22007
- const {
22008
- initDataType,
22009
- initData
22010
- } = event;
22011
- this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22012
-
22013
- // Ignore event when initData is null
22014
- if (initData === null) {
22015
- return;
22016
- }
22017
- let keyId;
22018
- let keySystemDomain;
22019
- if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22020
- // Match sinf keyId to playlist skd://keyId=
22021
- const json = bin2str(new Uint8Array(initData));
22022
- try {
22023
- const sinf = base64Decode(JSON.parse(json).sinf);
22024
- const tenc = parseSinf(new Uint8Array(sinf));
22025
- if (!tenc) {
22026
- return;
22027
- }
22028
- keyId = tenc.subarray(8, 24);
22029
- keySystemDomain = KeySystems.FAIRPLAY;
22030
- } catch (error) {
22031
- this.warn('Failed to parse sinf "encrypted" event message initData');
22032
- return;
22033
- }
22034
- } else {
22035
- // Support clear-lead key-session creation (otherwise depend on playlist keys)
22036
- const psshInfo = parsePssh(initData);
22037
- if (psshInfo === null) {
22038
- return;
22039
- }
22040
- if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22041
- keyId = psshInfo.data.subarray(8, 24);
22042
- }
22043
- keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22044
- }
22045
- if (!keySystemDomain || !keyId) {
22046
- return;
22047
- }
22048
- const keyIdHex = Hex.hexDump(keyId);
22049
- const {
22050
- keyIdToKeySessionPromise,
22051
- mediaKeySessions
22052
- } = this;
22053
- let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22054
- for (let i = 0; i < mediaKeySessions.length; i++) {
22055
- // Match playlist key
22056
- const keyContext = mediaKeySessions[i];
22057
- const decryptdata = keyContext.decryptdata;
22058
- if (decryptdata.pssh || !decryptdata.keyId) {
22059
- continue;
22060
- }
22061
- const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22062
- if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22063
- keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22064
- delete keyIdToKeySessionPromise[oldKeyIdHex];
22065
- decryptdata.pssh = new Uint8Array(initData);
22066
- decryptdata.keyId = keyId;
22067
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22068
- return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22069
- });
22070
- break;
22071
- }
22072
- }
22073
- if (!keySessionContextPromise) {
22074
- // Clear-lead key (not encountered in playlist)
22075
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22076
- keySystem,
22077
- mediaKeys
22078
- }) => {
22079
- var _keySystemToKeySystem;
22080
- this.throwIfDestroyed();
22081
- const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22082
- decryptdata.pssh = new Uint8Array(initData);
22083
- decryptdata.keyId = keyId;
22084
- return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22085
- this.throwIfDestroyed();
22086
- const keySessionContext = this.createMediaKeySessionContext({
22087
- decryptdata,
22088
- keySystem,
22089
- mediaKeys
22090
- });
22091
- return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22092
- });
22093
- });
22094
- }
22095
- keySessionContextPromise.catch(error => this.handleError(error));
22096
- };
22097
- this.onWaitingForKey = event => {
22098
- this.log(`"${event.type}" event`);
22099
- };
21911
+ this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
21912
+ this.onWaitingForKey = this._onWaitingForKey.bind(this);
21913
+ this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
21914
+ this.log = logger.log.bind(logger, LOGGER_PREFIX);
21915
+ this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
21916
+ this.error = logger.error.bind(logger, LOGGER_PREFIX);
22100
21917
  this.hls = hls;
22101
21918
  this.config = hls.config;
22102
21919
  this.registerListeners();
@@ -22110,9 +21927,9 @@ class EMEController extends Logger {
22110
21927
  config.licenseXhrSetup = config.licenseResponseCallback = undefined;
22111
21928
  config.drmSystems = config.drmSystemOptions = {};
22112
21929
  // @ts-ignore
22113
- this.hls = this.config = this.keyIdToKeySessionPromise = null;
21930
+ this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
22114
21931
  // @ts-ignore
22115
- this.onMediaEncrypted = this.onWaitingForKey = null;
21932
+ this.config = null;
22116
21933
  }
22117
21934
  registerListeners() {
22118
21935
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -22376,6 +22193,100 @@ class EMEController extends Logger {
22376
22193
  }
22377
22194
  return this.attemptKeySystemAccess(keySystemsToAttempt);
22378
22195
  }
22196
+ _onMediaEncrypted(event) {
22197
+ const {
22198
+ initDataType,
22199
+ initData
22200
+ } = event;
22201
+ this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22202
+
22203
+ // Ignore event when initData is null
22204
+ if (initData === null) {
22205
+ return;
22206
+ }
22207
+ let keyId;
22208
+ let keySystemDomain;
22209
+ if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22210
+ // Match sinf keyId to playlist skd://keyId=
22211
+ const json = bin2str(new Uint8Array(initData));
22212
+ try {
22213
+ const sinf = base64Decode(JSON.parse(json).sinf);
22214
+ const tenc = parseSinf(new Uint8Array(sinf));
22215
+ if (!tenc) {
22216
+ return;
22217
+ }
22218
+ keyId = tenc.subarray(8, 24);
22219
+ keySystemDomain = KeySystems.FAIRPLAY;
22220
+ } catch (error) {
22221
+ this.warn('Failed to parse sinf "encrypted" event message initData');
22222
+ return;
22223
+ }
22224
+ } else {
22225
+ // Support clear-lead key-session creation (otherwise depend on playlist keys)
22226
+ const psshInfo = parsePssh(initData);
22227
+ if (psshInfo === null) {
22228
+ return;
22229
+ }
22230
+ if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22231
+ keyId = psshInfo.data.subarray(8, 24);
22232
+ }
22233
+ keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22234
+ }
22235
+ if (!keySystemDomain || !keyId) {
22236
+ return;
22237
+ }
22238
+ const keyIdHex = Hex.hexDump(keyId);
22239
+ const {
22240
+ keyIdToKeySessionPromise,
22241
+ mediaKeySessions
22242
+ } = this;
22243
+ let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22244
+ for (let i = 0; i < mediaKeySessions.length; i++) {
22245
+ // Match playlist key
22246
+ const keyContext = mediaKeySessions[i];
22247
+ const decryptdata = keyContext.decryptdata;
22248
+ if (decryptdata.pssh || !decryptdata.keyId) {
22249
+ continue;
22250
+ }
22251
+ const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22252
+ if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22253
+ keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22254
+ delete keyIdToKeySessionPromise[oldKeyIdHex];
22255
+ decryptdata.pssh = new Uint8Array(initData);
22256
+ decryptdata.keyId = keyId;
22257
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22258
+ return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22259
+ });
22260
+ break;
22261
+ }
22262
+ }
22263
+ if (!keySessionContextPromise) {
22264
+ // Clear-lead key (not encountered in playlist)
22265
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22266
+ keySystem,
22267
+ mediaKeys
22268
+ }) => {
22269
+ var _keySystemToKeySystem;
22270
+ this.throwIfDestroyed();
22271
+ const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22272
+ decryptdata.pssh = new Uint8Array(initData);
22273
+ decryptdata.keyId = keyId;
22274
+ return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22275
+ this.throwIfDestroyed();
22276
+ const keySessionContext = this.createMediaKeySessionContext({
22277
+ decryptdata,
22278
+ keySystem,
22279
+ mediaKeys
22280
+ });
22281
+ return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22282
+ });
22283
+ });
22284
+ }
22285
+ keySessionContextPromise.catch(error => this.handleError(error));
22286
+ }
22287
+ _onWaitingForKey(event) {
22288
+ this.log(`"${event.type}" event`);
22289
+ }
22379
22290
  attemptSetMediaKeys(keySystem, mediaKeys) {
22380
22291
  const queue = this.setMediaKeysQueue.slice();
22381
22292
  this.log(`Setting media-keys for "${keySystem}"`);
@@ -22968,6 +22879,20 @@ class SfItem {
22968
22879
  }
22969
22880
  }
22970
22881
 
22882
+ /**
22883
+ * A class to represent structured field tokens when `Symbol` is not available.
22884
+ *
22885
+ * @group Structured Field
22886
+ *
22887
+ * @beta
22888
+ */
22889
+ class SfToken {
22890
+ constructor(description) {
22891
+ this.description = void 0;
22892
+ this.description = description;
22893
+ }
22894
+ }
22895
+
22971
22896
  const DICT = 'Dict';
22972
22897
 
22973
22898
  function format(value) {
@@ -22991,27 +22916,29 @@ function throwError(action, src, type, cause) {
22991
22916
  });
22992
22917
  }
22993
22918
 
22994
- function serializeError(src, type, cause) {
22995
- return throwError('serialize', src, type, cause);
22996
- }
22919
+ const BARE_ITEM = 'Bare Item';
22997
22920
 
22998
- /**
22999
- * A class to represent structured field tokens when `Symbol` is not available.
23000
- *
23001
- * @group Structured Field
23002
- *
23003
- * @beta
23004
- */
23005
- class SfToken {
23006
- constructor(description) {
23007
- this.description = void 0;
23008
- this.description = description;
23009
- }
22921
+ const BOOLEAN = 'Boolean';
22922
+
22923
+ const BYTES = 'Byte Sequence';
22924
+
22925
+ const DECIMAL = 'Decimal';
22926
+
22927
+ const INTEGER = 'Integer';
22928
+
22929
+ function isInvalidInt(value) {
22930
+ return value < -999999999999999 || 999999999999999 < value;
23010
22931
  }
23011
22932
 
23012
- const BARE_ITEM = 'Bare Item';
22933
+ const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
23013
22934
 
23014
- const BOOLEAN = 'Boolean';
22935
+ const TOKEN = 'Token';
22936
+
22937
+ const KEY = 'Key';
22938
+
22939
+ function serializeError(src, type, cause) {
22940
+ return throwError('serialize', src, type, cause);
22941
+ }
23015
22942
 
23016
22943
  // 4.1.9. Serializing a Boolean
23017
22944
  //
@@ -23050,8 +22977,6 @@ function base64encode(binary) {
23050
22977
  return btoa(String.fromCharCode(...binary));
23051
22978
  }
23052
22979
 
23053
- const BYTES = 'Byte Sequence';
23054
-
23055
22980
  // 4.1.8. Serializing a Byte Sequence
23056
22981
  //
23057
22982
  // Given a Byte Sequence as input_bytes, return an ASCII string suitable
@@ -23083,12 +23008,6 @@ function serializeByteSequence(value) {
23083
23008
  return `:${base64encode(value)}:`;
23084
23009
  }
23085
23010
 
23086
- const INTEGER = 'Integer';
23087
-
23088
- function isInvalidInt(value) {
23089
- return value < -999999999999999 || 999999999999999 < value;
23090
- }
23091
-
23092
23011
  // 4.1.4. Serializing an Integer
23093
23012
  //
23094
23013
  // Given an Integer as input_integer, return an ASCII string suitable
@@ -23154,8 +23073,6 @@ function roundToEven(value, precision) {
23154
23073
  }
23155
23074
  }
23156
23075
 
23157
- const DECIMAL = 'Decimal';
23158
-
23159
23076
  // 4.1.5. Serializing a Decimal
23160
23077
  //
23161
23078
  // Given a decimal number as input_decimal, return an ASCII string
@@ -23201,8 +23118,6 @@ function serializeDecimal(value) {
23201
23118
 
23202
23119
  const STRING = 'String';
23203
23120
 
23204
- const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
23205
-
23206
23121
  // 4.1.6. Serializing a String
23207
23122
  //
23208
23123
  // Given a String as input_string, return an ASCII string suitable for
@@ -23238,8 +23153,6 @@ function symbolToStr(symbol) {
23238
23153
  return symbol.description || symbol.toString().slice(7, -1);
23239
23154
  }
23240
23155
 
23241
- const TOKEN = 'Token';
23242
-
23243
23156
  function serializeToken(token) {
23244
23157
  const value = symbolToStr(token);
23245
23158
  if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
@@ -23307,8 +23220,6 @@ function serializeBareItem(value) {
23307
23220
  }
23308
23221
  }
23309
23222
 
23310
- const KEY = 'Key';
23311
-
23312
23223
  // 4.1.1.3. Serializing a Key
23313
23224
  //
23314
23225
  // Given a key as input_key, return an ASCII string suitable for use in
@@ -23550,6 +23461,36 @@ function urlToRelativePath(url, base) {
23550
23461
  return toPath.join('/');
23551
23462
  }
23552
23463
 
23464
+ /**
23465
+ * Generate a random v4 UUID
23466
+ *
23467
+ * @returns A random v4 UUID
23468
+ *
23469
+ * @group Utils
23470
+ *
23471
+ * @beta
23472
+ */
23473
+ function uuid() {
23474
+ try {
23475
+ return crypto.randomUUID();
23476
+ } catch (error) {
23477
+ try {
23478
+ const url = URL.createObjectURL(new Blob());
23479
+ const uuid = url.toString();
23480
+ URL.revokeObjectURL(url);
23481
+ return uuid.slice(uuid.lastIndexOf('/') + 1);
23482
+ } catch (error) {
23483
+ let dt = new Date().getTime();
23484
+ const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
23485
+ const r = (dt + Math.random() * 16) % 16 | 0;
23486
+ dt = Math.floor(dt / 16);
23487
+ return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
23488
+ });
23489
+ return uuid;
23490
+ }
23491
+ }
23492
+ }
23493
+
23553
23494
  const toRounded = value => Math.round(value);
23554
23495
  const toUrlSafe = (value, options) => {
23555
23496
  if (options != null && options.baseUrl) {
@@ -23775,36 +23716,6 @@ function appendCmcdQuery(url, cmcd, options) {
23775
23716
  return `${url}${separator}${query}`;
23776
23717
  }
23777
23718
 
23778
- /**
23779
- * Generate a random v4 UUID
23780
- *
23781
- * @returns A random v4 UUID
23782
- *
23783
- * @group Utils
23784
- *
23785
- * @beta
23786
- */
23787
- function uuid() {
23788
- try {
23789
- return crypto.randomUUID();
23790
- } catch (error) {
23791
- try {
23792
- const url = URL.createObjectURL(new Blob());
23793
- const uuid = url.toString();
23794
- URL.revokeObjectURL(url);
23795
- return uuid.slice(uuid.lastIndexOf('/') + 1);
23796
- } catch (error) {
23797
- let dt = new Date().getTime();
23798
- const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
23799
- const r = (dt + Math.random() * 16) % 16 | 0;
23800
- dt = Math.floor(dt / 16);
23801
- return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
23802
- });
23803
- return uuid;
23804
- }
23805
- }
23806
- }
23807
-
23808
23719
  /**
23809
23720
  * Controller to deal with Common Media Client Data (CMCD)
23810
23721
  * @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
@@ -23868,12 +23779,6 @@ class CMCDController {
23868
23779
  data.tb = this.getTopBandwidth(ot) / 1000;
23869
23780
  data.bl = this.getBufferLength(ot);
23870
23781
  }
23871
- const next = this.getNextFrag(fragment);
23872
- if (next) {
23873
- if (next.url && next.url !== fragment.url) {
23874
- data.nor = next.url;
23875
- }
23876
- }
23877
23782
  this.apply(context, data);
23878
23783
  } catch (error) {
23879
23784
  logger.warn('Could not generate segment CMCD data.', error);
@@ -23966,7 +23871,7 @@ class CMCDController {
23966
23871
  data.su = this.buffering;
23967
23872
  }
23968
23873
 
23969
- // TODO: Implement rtp, nrr, dl
23874
+ // TODO: Implement rtp, nrr, nor, dl
23970
23875
 
23971
23876
  const {
23972
23877
  includeKeys
@@ -23977,28 +23882,15 @@ class CMCDController {
23977
23882
  return acc;
23978
23883
  }, {});
23979
23884
  }
23980
- const options = {
23981
- baseUrl: context.url
23982
- };
23983
23885
  if (this.useHeaders) {
23984
23886
  if (!context.headers) {
23985
23887
  context.headers = {};
23986
23888
  }
23987
- appendCmcdHeaders(context.headers, data, options);
23889
+ appendCmcdHeaders(context.headers, data);
23988
23890
  } else {
23989
- context.url = appendCmcdQuery(context.url, data, options);
23891
+ context.url = appendCmcdQuery(context.url, data);
23990
23892
  }
23991
23893
  }
23992
- getNextFrag(fragment) {
23993
- var _this$hls$levels$frag;
23994
- const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
23995
- if (levelDetails) {
23996
- const index = fragment.sn - levelDetails.startSN;
23997
- return levelDetails.fragments[index + 1];
23998
- }
23999
- return undefined;
24000
- }
24001
-
24002
23894
  /**
24003
23895
  * The CMCD object type.
24004
23896
  */
@@ -24127,10 +24019,10 @@ class CMCDController {
24127
24019
  }
24128
24020
 
24129
24021
  const PATHWAY_PENALTY_DURATION_MS = 300000;
24130
- class ContentSteeringController extends Logger {
24022
+ class ContentSteeringController {
24131
24023
  constructor(hls) {
24132
- super('content-steering', hls.logger);
24133
24024
  this.hls = void 0;
24025
+ this.log = void 0;
24134
24026
  this.loader = null;
24135
24027
  this.uri = null;
24136
24028
  this.pathwayId = '.';
@@ -24145,6 +24037,7 @@ class ContentSteeringController extends Logger {
24145
24037
  this.subtitleTracks = null;
24146
24038
  this.penalizedPathways = {};
24147
24039
  this.hls = hls;
24040
+ this.log = logger.log.bind(logger, `[content-steering]:`);
24148
24041
  this.registerListeners();
24149
24042
  }
24150
24043
  registerListeners() {
@@ -24268,7 +24161,7 @@ class ContentSteeringController extends Logger {
24268
24161
  errorAction.resolved = this.pathwayId !== errorPathway;
24269
24162
  }
24270
24163
  if (!errorAction.resolved) {
24271
- 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)}`);
24164
+ 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)}`);
24272
24165
  }
24273
24166
  }
24274
24167
  }
@@ -24439,7 +24332,7 @@ class ContentSteeringController extends Logger {
24439
24332
  onSuccess: (response, stats, context, networkDetails) => {
24440
24333
  this.log(`Loaded steering manifest: "${url}"`);
24441
24334
  const steeringData = response.data;
24442
- if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
24335
+ if (steeringData.VERSION !== 1) {
24443
24336
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
24444
24337
  return;
24445
24338
  }
@@ -25409,7 +25302,7 @@ function timelineConfig() {
25409
25302
  /**
25410
25303
  * @ignore
25411
25304
  */
25412
- function mergeConfig(defaultConfig, userConfig, logger) {
25305
+ function mergeConfig(defaultConfig, userConfig) {
25413
25306
  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
25414
25307
  throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
25415
25308
  }
@@ -25479,7 +25372,7 @@ function deepCpy(obj) {
25479
25372
  /**
25480
25373
  * @ignore
25481
25374
  */
25482
- function enableStreamingMode(config, logger) {
25375
+ function enableStreamingMode(config) {
25483
25376
  const currentLoader = config.loader;
25484
25377
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
25485
25378
  // If a developer has configured their own loader, respect that choice
@@ -25496,9 +25389,10 @@ function enableStreamingMode(config, logger) {
25496
25389
  }
25497
25390
  }
25498
25391
 
25392
+ let chromeOrFirefox;
25499
25393
  class LevelController extends BasePlaylistController {
25500
25394
  constructor(hls, contentSteeringController) {
25501
- super(hls, 'level-controller');
25395
+ super(hls, '[level-controller]');
25502
25396
  this._levels = [];
25503
25397
  this._firstLevel = -1;
25504
25398
  this._maxAutoLevel = -1;
@@ -25569,15 +25463,23 @@ class LevelController extends BasePlaylistController {
25569
25463
  let videoCodecFound = false;
25570
25464
  let audioCodecFound = false;
25571
25465
  data.levels.forEach(levelParsed => {
25572
- var _videoCodec;
25466
+ var _audioCodec, _videoCodec;
25573
25467
  const attributes = levelParsed.attrs;
25468
+
25469
+ // erase audio codec info if browser does not support mp4a.40.34.
25470
+ // demuxer will autodetect codec and fallback to mpeg/audio
25574
25471
  let {
25575
25472
  audioCodec,
25576
25473
  videoCodec
25577
25474
  } = levelParsed;
25475
+ if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
25476
+ chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
25477
+ if (chromeOrFirefox) {
25478
+ levelParsed.audioCodec = audioCodec = undefined;
25479
+ }
25480
+ }
25578
25481
  if (audioCodec) {
25579
- // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
25580
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
25482
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
25581
25483
  }
25582
25484
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
25583
25485
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -26163,8 +26065,6 @@ class KeyLoader {
26163
26065
  }
26164
26066
  return this.loadKeyEME(keyInfo, frag);
26165
26067
  case 'AES-128':
26166
- case 'AES-256':
26167
- case 'AES-256-CTR':
26168
26068
  return this.loadKeyHTTP(keyInfo, frag);
26169
26069
  default:
26170
26070
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -26302,9 +26202,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
26302
26202
  const MAX_START_GAP_JUMP = 2.0;
26303
26203
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
26304
26204
  const SKIP_BUFFER_RANGE_START = 0.05;
26305
- class GapController extends Logger {
26205
+ class GapController {
26306
26206
  constructor(config, media, fragmentTracker, hls) {
26307
- super('gap-controller', hls.logger);
26308
26207
  this.config = void 0;
26309
26208
  this.media = null;
26310
26209
  this.fragmentTracker = void 0;
@@ -26314,7 +26213,6 @@ class GapController extends Logger {
26314
26213
  this.stalled = null;
26315
26214
  this.moved = false;
26316
26215
  this.seeking = false;
26317
- this.ended = 0;
26318
26216
  this.config = config;
26319
26217
  this.media = media;
26320
26218
  this.fragmentTracker = fragmentTracker;
@@ -26332,7 +26230,7 @@ class GapController extends Logger {
26332
26230
  *
26333
26231
  * @param lastCurrentTime - Previously read playhead position
26334
26232
  */
26335
- poll(lastCurrentTime, activeFrag, levelDetails, state) {
26233
+ poll(lastCurrentTime, activeFrag) {
26336
26234
  const {
26337
26235
  config,
26338
26236
  media,
@@ -26351,7 +26249,6 @@ class GapController extends Logger {
26351
26249
 
26352
26250
  // The playhead is moving, no-op
26353
26251
  if (currentTime !== lastCurrentTime) {
26354
- this.ended = 0;
26355
26252
  this.moved = true;
26356
26253
  if (!seeking) {
26357
26254
  this.nudgeRetry = 0;
@@ -26360,7 +26257,7 @@ class GapController extends Logger {
26360
26257
  // The playhead is now moving, but was previously stalled
26361
26258
  if (this.stallReported) {
26362
26259
  const _stalledDuration = self.performance.now() - stalled;
26363
- this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
26260
+ logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
26364
26261
  this.stallReported = false;
26365
26262
  }
26366
26263
  this.stalled = null;
@@ -26396,6 +26293,7 @@ class GapController extends Logger {
26396
26293
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
26397
26294
  // The addition poll gives the browser a chance to jump the gap for us
26398
26295
  if (!this.moved && this.stalled !== null) {
26296
+ var _level$details;
26399
26297
  // There is no playable buffer (seeked, waiting for buffer)
26400
26298
  const isBuffered = bufferInfo.len > 0;
26401
26299
  if (!isBuffered && !nextStart) {
@@ -26407,8 +26305,9 @@ class GapController extends Logger {
26407
26305
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
26408
26306
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
26409
26307
  // that begins over 1 target duration after the video start position.
26410
- const isLive = !!(levelDetails != null && levelDetails.live);
26411
- const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
26308
+ const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
26309
+ const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
26310
+ const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
26412
26311
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
26413
26312
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
26414
26313
  if (!media.paused) {
@@ -26426,17 +26325,6 @@ class GapController extends Logger {
26426
26325
  }
26427
26326
  const stalledDuration = tnow - stalled;
26428
26327
  if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
26429
- // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
26430
- if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
26431
- if (stalledDuration < 1000 || this.ended) {
26432
- return;
26433
- }
26434
- this.ended = currentTime;
26435
- this.hls.trigger(Events.MEDIA_ENDED, {
26436
- stalled: true
26437
- });
26438
- return;
26439
- }
26440
26328
  // Report stalling after trying to fix
26441
26329
  this._reportStall(bufferInfo);
26442
26330
  if (!this.media) {
@@ -26480,7 +26368,7 @@ class GapController extends Logger {
26480
26368
  // needs to cross some sort of threshold covering all source-buffers content
26481
26369
  // to start playing properly.
26482
26370
  if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
26483
- this.warn('Trying to nudge playhead over buffer-hole');
26371
+ logger.warn('Trying to nudge playhead over buffer-hole');
26484
26372
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
26485
26373
  // We only try to jump the hole if it's under the configured size
26486
26374
  // Reset stalled so to rearm watchdog timer
@@ -26504,7 +26392,7 @@ class GapController extends Logger {
26504
26392
  // Report stalled error once
26505
26393
  this.stallReported = true;
26506
26394
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
26507
- this.warn(error.message);
26395
+ logger.warn(error.message);
26508
26396
  hls.trigger(Events.ERROR, {
26509
26397
  type: ErrorTypes.MEDIA_ERROR,
26510
26398
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -26572,7 +26460,7 @@ class GapController extends Logger {
26572
26460
  }
26573
26461
  }
26574
26462
  const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
26575
- this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
26463
+ logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
26576
26464
  this.moved = true;
26577
26465
  this.stalled = null;
26578
26466
  media.currentTime = targetTime;
@@ -26613,7 +26501,7 @@ class GapController extends Logger {
26613
26501
  const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
26614
26502
  // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
26615
26503
  const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
26616
- this.warn(error.message);
26504
+ logger.warn(error.message);
26617
26505
  media.currentTime = targetTime;
26618
26506
  hls.trigger(Events.ERROR, {
26619
26507
  type: ErrorTypes.MEDIA_ERROR,
@@ -26623,7 +26511,7 @@ class GapController extends Logger {
26623
26511
  });
26624
26512
  } else {
26625
26513
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
26626
- this.error(error.message);
26514
+ logger.error(error.message);
26627
26515
  hls.trigger(Events.ERROR, {
26628
26516
  type: ErrorTypes.MEDIA_ERROR,
26629
26517
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -26638,7 +26526,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
26638
26526
 
26639
26527
  class StreamController extends BaseStreamController {
26640
26528
  constructor(hls, fragmentTracker, keyLoader) {
26641
- super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
26529
+ super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
26642
26530
  this.audioCodecSwap = false;
26643
26531
  this.gapController = null;
26644
26532
  this.level = -1;
@@ -26646,43 +26534,27 @@ class StreamController extends BaseStreamController {
26646
26534
  this.altAudio = false;
26647
26535
  this.audioOnly = false;
26648
26536
  this.fragPlaying = null;
26537
+ this.onvplaying = null;
26538
+ this.onvseeked = null;
26649
26539
  this.fragLastKbps = 0;
26650
26540
  this.couldBacktrack = false;
26651
26541
  this.backtrackFragment = null;
26652
26542
  this.audioCodecSwitch = false;
26653
26543
  this.videoBuffer = null;
26654
- this.onMediaPlaying = () => {
26655
- // tick to speed up FRAG_CHANGED triggering
26656
- this.tick();
26657
- };
26658
- this.onMediaSeeked = () => {
26659
- const media = this.media;
26660
- const currentTime = media ? media.currentTime : null;
26661
- if (isFiniteNumber(currentTime)) {
26662
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
26663
- }
26664
-
26665
- // If seeked was issued before buffer was appended do not tick immediately
26666
- const bufferInfo = this.getMainFwdBufferInfo();
26667
- if (bufferInfo === null || bufferInfo.len === 0) {
26668
- this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
26669
- return;
26670
- }
26671
-
26672
- // tick to speed up FRAG_CHANGED triggering
26673
- this.tick();
26674
- };
26675
- this.registerListeners();
26544
+ this._registerListeners();
26676
26545
  }
26677
- registerListeners() {
26678
- super.registerListeners();
26546
+ _registerListeners() {
26679
26547
  const {
26680
26548
  hls
26681
26549
  } = this;
26550
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
26551
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
26552
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
26682
26553
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
26683
26554
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
26684
26555
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
26685
26556
  hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26557
+ hls.on(Events.ERROR, this.onError, this);
26686
26558
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
26687
26559
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
26688
26560
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -26690,14 +26562,17 @@ class StreamController extends BaseStreamController {
26690
26562
  hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
26691
26563
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
26692
26564
  }
26693
- unregisterListeners() {
26694
- super.unregisterListeners();
26565
+ _unregisterListeners() {
26695
26566
  const {
26696
26567
  hls
26697
26568
  } = this;
26569
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
26570
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
26571
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
26698
26572
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
26699
26573
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
26700
26574
  hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26575
+ hls.off(Events.ERROR, this.onError, this);
26701
26576
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
26702
26577
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
26703
26578
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -26706,9 +26581,7 @@ class StreamController extends BaseStreamController {
26706
26581
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
26707
26582
  }
26708
26583
  onHandlerDestroying() {
26709
- // @ts-ignore
26710
- this.onMediaPlaying = this.onMediaSeeked = null;
26711
- this.unregisterListeners();
26584
+ this._unregisterListeners();
26712
26585
  super.onHandlerDestroying();
26713
26586
  }
26714
26587
  startLoad(startPosition) {
@@ -27035,17 +26908,20 @@ class StreamController extends BaseStreamController {
27035
26908
  onMediaAttached(event, data) {
27036
26909
  super.onMediaAttached(event, data);
27037
26910
  const media = data.media;
27038
- media.addEventListener('playing', this.onMediaPlaying);
27039
- media.addEventListener('seeked', this.onMediaSeeked);
26911
+ this.onvplaying = this.onMediaPlaying.bind(this);
26912
+ this.onvseeked = this.onMediaSeeked.bind(this);
26913
+ media.addEventListener('playing', this.onvplaying);
26914
+ media.addEventListener('seeked', this.onvseeked);
27040
26915
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
27041
26916
  }
27042
26917
  onMediaDetaching() {
27043
26918
  const {
27044
26919
  media
27045
26920
  } = this;
27046
- if (media) {
27047
- media.removeEventListener('playing', this.onMediaPlaying);
27048
- media.removeEventListener('seeked', this.onMediaSeeked);
26921
+ if (media && this.onvplaying && this.onvseeked) {
26922
+ media.removeEventListener('playing', this.onvplaying);
26923
+ media.removeEventListener('seeked', this.onvseeked);
26924
+ this.onvplaying = this.onvseeked = null;
27049
26925
  this.videoBuffer = null;
27050
26926
  }
27051
26927
  this.fragPlaying = null;
@@ -27055,6 +26931,27 @@ class StreamController extends BaseStreamController {
27055
26931
  }
27056
26932
  super.onMediaDetaching();
27057
26933
  }
26934
+ onMediaPlaying() {
26935
+ // tick to speed up FRAG_CHANGED triggering
26936
+ this.tick();
26937
+ }
26938
+ onMediaSeeked() {
26939
+ const media = this.media;
26940
+ const currentTime = media ? media.currentTime : null;
26941
+ if (isFiniteNumber(currentTime)) {
26942
+ this.log(`Media seeked to ${currentTime.toFixed(3)}`);
26943
+ }
26944
+
26945
+ // If seeked was issued before buffer was appended do not tick immediately
26946
+ const bufferInfo = this.getMainFwdBufferInfo();
26947
+ if (bufferInfo === null || bufferInfo.len === 0) {
26948
+ this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
26949
+ return;
26950
+ }
26951
+
26952
+ // tick to speed up FRAG_CHANGED triggering
26953
+ this.tick();
26954
+ }
27058
26955
  onManifestLoading() {
27059
26956
  // reset buffer on manifest loading
27060
26957
  this.log('Trigger BUFFER_RESET');
@@ -27346,10 +27243,8 @@ class StreamController extends BaseStreamController {
27346
27243
  }
27347
27244
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
27348
27245
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
27349
- const state = this.state;
27350
- const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
27351
- const levelDetails = this.getLevelDetails();
27352
- gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
27246
+ const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
27247
+ gapController.poll(this.lastCurrentTime, activeFrag);
27353
27248
  }
27354
27249
  this.lastCurrentTime = media.currentTime;
27355
27250
  }
@@ -27787,7 +27682,7 @@ class Hls {
27787
27682
  * Get the video-dev/hls.js package version.
27788
27683
  */
27789
27684
  static get version() {
27790
- return "1.5.2-0.canary.9934";
27685
+ return "1.5.3";
27791
27686
  }
27792
27687
 
27793
27688
  /**
@@ -27850,10 +27745,6 @@ class Hls {
27850
27745
  * The configuration object provided on player instantiation.
27851
27746
  */
27852
27747
  this.userConfig = void 0;
27853
- /**
27854
- * The logger functions used by this player instance, configured on player instantiation.
27855
- */
27856
- this.logger = void 0;
27857
27748
  this.coreComponents = void 0;
27858
27749
  this.networkControllers = void 0;
27859
27750
  this.started = false;
@@ -27873,11 +27764,11 @@ class Hls {
27873
27764
  this._media = null;
27874
27765
  this.url = null;
27875
27766
  this.triggeringException = void 0;
27876
- const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
27877
- const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
27767
+ enableLogs(userConfig.debug || false, 'Hls instance');
27768
+ const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
27878
27769
  this.userConfig = userConfig;
27879
27770
  if (config.progressive) {
27880
- enableStreamingMode(config, logger);
27771
+ enableStreamingMode(config);
27881
27772
  }
27882
27773
 
27883
27774
  // core controllers and network loaders
@@ -27976,7 +27867,7 @@ class Hls {
27976
27867
  try {
27977
27868
  return this.emit(event, event, eventObject);
27978
27869
  } catch (error) {
27979
- this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
27870
+ logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
27980
27871
  // Prevent recursion in error event handlers that throw #5497
27981
27872
  if (!this.triggeringException) {
27982
27873
  this.triggeringException = true;
@@ -28002,7 +27893,7 @@ class Hls {
28002
27893
  * Dispose of the instance
28003
27894
  */
28004
27895
  destroy() {
28005
- this.logger.log('destroy');
27896
+ logger.log('destroy');
28006
27897
  this.trigger(Events.DESTROYING, undefined);
28007
27898
  this.detachMedia();
28008
27899
  this.removeAllListeners();
@@ -28023,7 +27914,7 @@ class Hls {
28023
27914
  * Attaches Hls.js to a media element
28024
27915
  */
28025
27916
  attachMedia(media) {
28026
- this.logger.log('attachMedia');
27917
+ logger.log('attachMedia');
28027
27918
  this._media = media;
28028
27919
  this.trigger(Events.MEDIA_ATTACHING, {
28029
27920
  media: media
@@ -28034,7 +27925,7 @@ class Hls {
28034
27925
  * Detach Hls.js from the media
28035
27926
  */
28036
27927
  detachMedia() {
28037
- this.logger.log('detachMedia');
27928
+ logger.log('detachMedia');
28038
27929
  this.trigger(Events.MEDIA_DETACHING, undefined);
28039
27930
  this._media = null;
28040
27931
  }
@@ -28051,7 +27942,7 @@ class Hls {
28051
27942
  });
28052
27943
  this._autoLevelCapping = -1;
28053
27944
  this._maxHdcpLevel = null;
28054
- this.logger.log(`loadSource:${loadingSource}`);
27945
+ logger.log(`loadSource:${loadingSource}`);
28055
27946
  if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
28056
27947
  this.detachMedia();
28057
27948
  this.attachMedia(media);
@@ -28070,7 +27961,7 @@ class Hls {
28070
27961
  * Defaults to -1 (None: starts from earliest point)
28071
27962
  */
28072
27963
  startLoad(startPosition = -1) {
28073
- this.logger.log(`startLoad(${startPosition})`);
27964
+ logger.log(`startLoad(${startPosition})`);
28074
27965
  this.started = true;
28075
27966
  this.networkControllers.forEach(controller => {
28076
27967
  controller.startLoad(startPosition);
@@ -28081,7 +27972,7 @@ class Hls {
28081
27972
  * Stop loading of any stream data.
28082
27973
  */
28083
27974
  stopLoad() {
28084
- this.logger.log('stopLoad');
27975
+ logger.log('stopLoad');
28085
27976
  this.started = false;
28086
27977
  this.networkControllers.forEach(controller => {
28087
27978
  controller.stopLoad();
@@ -28117,7 +28008,7 @@ class Hls {
28117
28008
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
28118
28009
  */
28119
28010
  swapAudioCodec() {
28120
- this.logger.log('swapAudioCodec');
28011
+ logger.log('swapAudioCodec');
28121
28012
  this.streamController.swapAudioCodec();
28122
28013
  }
28123
28014
 
@@ -28128,7 +28019,7 @@ class Hls {
28128
28019
  * Automatic recovery of media-errors by this process is configurable.
28129
28020
  */
28130
28021
  recoverMediaError() {
28131
- this.logger.log('recoverMediaError');
28022
+ logger.log('recoverMediaError');
28132
28023
  const media = this._media;
28133
28024
  this.detachMedia();
28134
28025
  if (media) {
@@ -28158,7 +28049,7 @@ class Hls {
28158
28049
  * 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.
28159
28050
  */
28160
28051
  set currentLevel(newLevel) {
28161
- this.logger.log(`set currentLevel:${newLevel}`);
28052
+ logger.log(`set currentLevel:${newLevel}`);
28162
28053
  this.levelController.manualLevel = newLevel;
28163
28054
  this.streamController.immediateLevelSwitch();
28164
28055
  }
@@ -28177,7 +28068,7 @@ class Hls {
28177
28068
  * @param newLevel - Pass -1 for automatic level selection
28178
28069
  */
28179
28070
  set nextLevel(newLevel) {
28180
- this.logger.log(`set nextLevel:${newLevel}`);
28071
+ logger.log(`set nextLevel:${newLevel}`);
28181
28072
  this.levelController.manualLevel = newLevel;
28182
28073
  this.streamController.nextLevelSwitch();
28183
28074
  }
@@ -28196,7 +28087,7 @@ class Hls {
28196
28087
  * @param newLevel - Pass -1 for automatic level selection
28197
28088
  */
28198
28089
  set loadLevel(newLevel) {
28199
- this.logger.log(`set loadLevel:${newLevel}`);
28090
+ logger.log(`set loadLevel:${newLevel}`);
28200
28091
  this.levelController.manualLevel = newLevel;
28201
28092
  }
28202
28093
 
@@ -28227,7 +28118,7 @@ class Hls {
28227
28118
  * Sets "first-level", see getter.
28228
28119
  */
28229
28120
  set firstLevel(newLevel) {
28230
- this.logger.log(`set firstLevel:${newLevel}`);
28121
+ logger.log(`set firstLevel:${newLevel}`);
28231
28122
  this.levelController.firstLevel = newLevel;
28232
28123
  }
28233
28124
 
@@ -28252,7 +28143,7 @@ class Hls {
28252
28143
  * (determined from download of first segment)
28253
28144
  */
28254
28145
  set startLevel(newLevel) {
28255
- this.logger.log(`set startLevel:${newLevel}`);
28146
+ logger.log(`set startLevel:${newLevel}`);
28256
28147
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
28257
28148
  if (newLevel !== -1) {
28258
28149
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -28327,7 +28218,7 @@ class Hls {
28327
28218
  */
28328
28219
  set autoLevelCapping(newLevel) {
28329
28220
  if (this._autoLevelCapping !== newLevel) {
28330
- this.logger.log(`set autoLevelCapping:${newLevel}`);
28221
+ logger.log(`set autoLevelCapping:${newLevel}`);
28331
28222
  this._autoLevelCapping = newLevel;
28332
28223
  this.levelController.checkMaxAutoUpdated();
28333
28224
  }