hls.js 1.5.7-0.canary.10040 → 1.5.7

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 (72) hide show
  1. package/README.md +1 -2
  2. package/dist/hls-demo.js +0 -10
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +1283 -2293
  5. package/dist/hls.js.d.ts +84 -97
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1030 -1435
  8. package/dist/hls.light.js.map +1 -1
  9. package/dist/hls.light.min.js +1 -1
  10. package/dist/hls.light.min.js.map +1 -1
  11. package/dist/hls.light.mjs +809 -1209
  12. package/dist/hls.light.mjs.map +1 -1
  13. package/dist/hls.min.js +1 -1
  14. package/dist/hls.min.js.map +1 -1
  15. package/dist/hls.mjs +1039 -2030
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +22 -22
  20. package/src/config.ts +2 -3
  21. package/src/controller/abr-controller.ts +20 -24
  22. package/src/controller/audio-stream-controller.ts +74 -68
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +8 -20
  25. package/src/controller/base-stream-controller.ts +36 -157
  26. package/src/controller/buffer-controller.ts +99 -226
  27. package/src/controller/buffer-operation-queue.ts +19 -16
  28. package/src/controller/cap-level-controller.ts +2 -2
  29. package/src/controller/cmcd-controller.ts +6 -27
  30. package/src/controller/content-steering-controller.ts +6 -8
  31. package/src/controller/eme-controller.ts +22 -9
  32. package/src/controller/error-controller.ts +8 -6
  33. package/src/controller/fps-controller.ts +3 -2
  34. package/src/controller/fragment-tracker.ts +11 -15
  35. package/src/controller/gap-controller.ts +16 -43
  36. package/src/controller/latency-controller.ts +11 -9
  37. package/src/controller/level-controller.ts +18 -12
  38. package/src/controller/stream-controller.ts +31 -36
  39. package/src/controller/subtitle-stream-controller.ts +40 -28
  40. package/src/controller/subtitle-track-controller.ts +3 -5
  41. package/src/controller/timeline-controller.ts +30 -23
  42. package/src/crypt/aes-crypto.ts +2 -21
  43. package/src/crypt/decrypter.ts +18 -32
  44. package/src/crypt/fast-aes-key.ts +5 -24
  45. package/src/demux/audio/adts.ts +4 -9
  46. package/src/demux/sample-aes.ts +0 -2
  47. package/src/demux/transmuxer-interface.ts +12 -4
  48. package/src/demux/transmuxer-worker.ts +4 -4
  49. package/src/demux/transmuxer.ts +3 -16
  50. package/src/demux/tsdemuxer.ts +37 -71
  51. package/src/demux/video/avc-video-parser.ts +119 -208
  52. package/src/demux/video/base-video-parser.ts +2 -134
  53. package/src/demux/video/exp-golomb.ts +208 -0
  54. package/src/events.ts +0 -7
  55. package/src/hls.ts +37 -49
  56. package/src/loader/fragment-loader.ts +2 -9
  57. package/src/loader/key-loader.ts +0 -2
  58. package/src/loader/level-key.ts +9 -10
  59. package/src/loader/playlist-loader.ts +5 -4
  60. package/src/remux/mp4-generator.ts +1 -196
  61. package/src/remux/mp4-remuxer.ts +7 -23
  62. package/src/task-loop.ts +2 -5
  63. package/src/types/component-api.ts +0 -2
  64. package/src/types/demuxer.ts +0 -3
  65. package/src/types/events.ts +0 -4
  66. package/src/utils/buffer-helper.ts +31 -12
  67. package/src/utils/codecs.ts +5 -34
  68. package/src/utils/logger.ts +24 -54
  69. package/src/utils/mp4-tools.ts +2 -4
  70. package/src/crypt/decrypter-aes-mode.ts +0 -4
  71. package/src/demux/video/hevc-video-parser.ts +0 -746
  72. package/src/utils/encryption-methods-util.ts +0 -21
package/dist/hls.mjs CHANGED
@@ -256,7 +256,6 @@ let Events = /*#__PURE__*/function (Events) {
256
256
  Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
257
257
  Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
258
258
  Events["MEDIA_DETACHED"] = "hlsMediaDetached";
259
- Events["MEDIA_ENDED"] = "hlsMediaEnded";
260
259
  Events["BUFFER_RESET"] = "hlsBufferReset";
261
260
  Events["BUFFER_CODECS"] = "hlsBufferCodecs";
262
261
  Events["BUFFER_CREATED"] = "hlsBufferCreated";
@@ -370,6 +369,58 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
370
369
  return ErrorDetails;
371
370
  }({});
372
371
 
372
+ const noop = function noop() {};
373
+ const fakeLogger = {
374
+ trace: noop,
375
+ debug: noop,
376
+ log: noop,
377
+ warn: noop,
378
+ info: noop,
379
+ error: noop
380
+ };
381
+ let exportedLogger = fakeLogger;
382
+
383
+ // let lastCallTime;
384
+ // function formatMsgWithTimeInfo(type, msg) {
385
+ // const now = Date.now();
386
+ // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
387
+ // lastCallTime = now;
388
+ // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
389
+ // return msg;
390
+ // }
391
+
392
+ function consolePrintFn(type) {
393
+ const func = self.console[type];
394
+ if (func) {
395
+ return func.bind(self.console, `[${type}] >`);
396
+ }
397
+ return noop;
398
+ }
399
+ function exportLoggerFunctions(debugConfig, ...functions) {
400
+ functions.forEach(function (type) {
401
+ exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
402
+ });
403
+ }
404
+ function enableLogs(debugConfig, id) {
405
+ // check that console is available
406
+ if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
407
+ exportLoggerFunctions(debugConfig,
408
+ // Remove out from list here to hard-disable a log-level
409
+ // 'trace',
410
+ 'debug', 'log', 'info', 'warn', 'error');
411
+ // Some browsers don't allow to use bind on console object anyway
412
+ // fallback to default if needed
413
+ try {
414
+ exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.7"}`);
415
+ } catch (e) {
416
+ exportedLogger = fakeLogger;
417
+ }
418
+ } else {
419
+ exportedLogger = fakeLogger;
420
+ }
421
+ }
422
+ const logger = exportedLogger;
423
+
373
424
  const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
374
425
  const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
375
426
 
@@ -451,79 +502,6 @@ class AttrList {
451
502
  }
452
503
  }
453
504
 
454
- class Logger {
455
- constructor(label, logger) {
456
- this.trace = void 0;
457
- this.debug = void 0;
458
- this.log = void 0;
459
- this.warn = void 0;
460
- this.info = void 0;
461
- this.error = void 0;
462
- const lb = `[${label}]:`;
463
- this.trace = noop;
464
- this.debug = logger.debug.bind(null, lb);
465
- this.log = logger.log.bind(null, lb);
466
- this.warn = logger.warn.bind(null, lb);
467
- this.info = logger.info.bind(null, lb);
468
- this.error = logger.error.bind(null, lb);
469
- }
470
- }
471
- const noop = function noop() {};
472
- const fakeLogger = {
473
- trace: noop,
474
- debug: noop,
475
- log: noop,
476
- warn: noop,
477
- info: noop,
478
- error: noop
479
- };
480
- function createLogger() {
481
- return _extends({}, fakeLogger);
482
- }
483
-
484
- // let lastCallTime;
485
- // function formatMsgWithTimeInfo(type, msg) {
486
- // const now = Date.now();
487
- // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
488
- // lastCallTime = now;
489
- // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
490
- // return msg;
491
- // }
492
-
493
- function consolePrintFn(type, id) {
494
- const func = self.console[type];
495
- return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
496
- }
497
- function getLoggerFn(key, debugConfig, id) {
498
- return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
499
- }
500
- const exportedLogger = createLogger();
501
- function enableLogs(debugConfig, context, id) {
502
- // check that console is available
503
- const newLogger = createLogger();
504
- if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
505
- const keys = [
506
- // Remove out from list here to hard-disable a log-level
507
- // 'trace',
508
- 'debug', 'log', 'info', 'warn', 'error'];
509
- keys.forEach(key => {
510
- newLogger[key] = getLoggerFn(key, debugConfig, id);
511
- });
512
- // Some browsers don't allow to use bind on console object anyway
513
- // fallback to default if needed
514
- try {
515
- newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.7-0.canary.10040"}`);
516
- } catch (e) {
517
- /* log fn threw an exception. All logger methods are no-ops. */
518
- return createLogger();
519
- }
520
- }
521
- // global exported logger uses the log methods from last call to `enableLogs`
522
- _extends(exportedLogger, newLogger);
523
- return newLogger;
524
- }
525
- const logger = exportedLogger;
526
-
527
505
  // Avoid exporting const enum so that these values can be inlined
528
506
 
529
507
  function isDateRangeCueAttribute(attrName) {
@@ -1058,26 +1036,6 @@ function strToUtf8array(str) {
1058
1036
  return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
1059
1037
  }
1060
1038
 
1061
- var DecrypterAesMode = {
1062
- cbc: 0,
1063
- ctr: 1
1064
- };
1065
-
1066
- function isFullSegmentEncryption(method) {
1067
- return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
1068
- }
1069
- function getAesModeFromFullSegmentMethod(method) {
1070
- switch (method) {
1071
- case 'AES-128':
1072
- case 'AES-256':
1073
- return DecrypterAesMode.cbc;
1074
- case 'AES-256-CTR':
1075
- return DecrypterAesMode.ctr;
1076
- default:
1077
- throw new Error(`invalid full segment method ${method}`);
1078
- }
1079
- }
1080
-
1081
1039
  /** returns `undefined` is `self` is missing, e.g. in node */
1082
1040
  const optionalSelf = typeof self !== 'undefined' ? self : undefined;
1083
1041
 
@@ -1826,7 +1784,7 @@ function parseStsd(stsd) {
1826
1784
  {
1827
1785
  const codecBox = findBox(sampleEntries, [fourCC])[0];
1828
1786
  const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
1829
- if (esdsBox && esdsBox.length > 7) {
1787
+ if (esdsBox && esdsBox.length > 12) {
1830
1788
  let i = 4;
1831
1789
  // ES Descriptor tag
1832
1790
  if (esdsBox[i++] !== 0x03) {
@@ -1941,9 +1899,7 @@ function parseStsd(stsd) {
1941
1899
  }
1942
1900
  function skipBERInteger(bytes, i) {
1943
1901
  const limit = i + 5;
1944
- while (bytes[i++] & 0x80 && i < limit) {
1945
- /* do nothing */
1946
- }
1902
+ while (bytes[i++] & 0x80 && i < limit) {}
1947
1903
  return i;
1948
1904
  }
1949
1905
  function toHex(x) {
@@ -2732,12 +2688,12 @@ class LevelKey {
2732
2688
  this.keyFormatVersions = formatversions;
2733
2689
  this.iv = iv;
2734
2690
  this.encrypted = method ? method !== 'NONE' : false;
2735
- this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
2691
+ this.isCommonEncryption = this.encrypted && method !== 'AES-128';
2736
2692
  }
2737
2693
  isSupported() {
2738
2694
  // If it's Segment encryption or No encryption, just select that key system
2739
2695
  if (this.method) {
2740
- if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
2696
+ if (this.method === 'AES-128' || this.method === 'NONE') {
2741
2697
  return true;
2742
2698
  }
2743
2699
  if (this.keyFormat === 'identity') {
@@ -2759,13 +2715,14 @@ class LevelKey {
2759
2715
  if (!this.encrypted || !this.uri) {
2760
2716
  return null;
2761
2717
  }
2762
- if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
2718
+ if (this.method === 'AES-128' && this.uri && !this.iv) {
2763
2719
  if (typeof sn !== 'number') {
2764
2720
  // We are fetching decryption data for a initialization segment
2765
- // If the segment was encrypted with AES-128/256
2721
+ // If the segment was encrypted with AES-128
2766
2722
  // It must have an IV defined. We cannot substitute the Segment Number in.
2767
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2768
-
2723
+ if (this.method === 'AES-128' && !this.iv) {
2724
+ logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2725
+ }
2769
2726
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
2770
2727
  sn = 0;
2771
2728
  }
@@ -3044,28 +3001,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
3044
3001
  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
3045
3002
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
3046
3003
  }
3004
+
3005
+ // Idealy fLaC and Opus would be first (spec-compliant) but
3006
+ // some browsers will report that fLaC is supported then fail.
3007
+ // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3047
3008
  const codecsToCheck = {
3048
- // Idealy fLaC and Opus would be first (spec-compliant) but
3049
- // some browsers will report that fLaC is supported then fail.
3050
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3051
3009
  flac: ['flac', 'fLaC', 'FLAC'],
3052
- opus: ['opus', 'Opus'],
3053
- // Replace audio codec info if browser does not support mp4a.40.34,
3054
- // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
3055
- 'mp4a.40.34': ['mp3']
3010
+ opus: ['opus', 'Opus']
3056
3011
  }[lowerCaseCodec];
3057
3012
  for (let i = 0; i < codecsToCheck.length; i++) {
3058
- var _getMediaSource;
3059
3013
  if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
3060
3014
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
3061
3015
  return codecsToCheck[i];
3062
- } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
3063
- return '';
3064
3016
  }
3065
3017
  }
3066
3018
  return lowerCaseCodec;
3067
3019
  }
3068
- const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
3020
+ const AUDIO_CODEC_REGEXP = /flac|opus/i;
3069
3021
  function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
3070
3022
  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
3071
3023
  }
@@ -3088,16 +3040,6 @@ function convertAVC1ToAVCOTI(codec) {
3088
3040
  }
3089
3041
  return codec;
3090
3042
  }
3091
- function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
3092
- const MediaSource = getMediaSource(preferManagedMediaSource) || {
3093
- isTypeSupported: () => false
3094
- };
3095
- return {
3096
- mpeg: MediaSource.isTypeSupported('audio/mpeg'),
3097
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
3098
- ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
3099
- };
3100
- }
3101
3043
 
3102
3044
  const MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g;
3103
3045
  const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -3947,10 +3889,10 @@ class PlaylistLoader {
3947
3889
  const loaderContext = loader.context;
3948
3890
  if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
3949
3891
  // same URL can't overlap
3950
- this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
3892
+ logger.trace('[playlist-loader]: playlist request ongoing');
3951
3893
  return;
3952
3894
  }
3953
- this.hls.logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3895
+ logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3954
3896
  loader.abort();
3955
3897
  }
3956
3898
 
@@ -4060,7 +4002,7 @@ class PlaylistLoader {
4060
4002
  // alt audio rendition in which quality levels (main)
4061
4003
  // contains both audio+video. but with mixed audio track not signaled
4062
4004
  if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
4063
- this.hls.logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
4005
+ logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
4064
4006
  audioTracks.unshift({
4065
4007
  type: 'main',
4066
4008
  name: 'main',
@@ -4159,7 +4101,7 @@ class PlaylistLoader {
4159
4101
  message += ` id: ${context.id} group-id: "${context.groupId}"`;
4160
4102
  }
4161
4103
  const error = new Error(message);
4162
- this.hls.logger.warn(`[playlist-loader]: ${message}`);
4104
+ logger.warn(`[playlist-loader]: ${message}`);
4163
4105
  let details = ErrorDetails.UNKNOWN;
4164
4106
  let fatal = false;
4165
4107
  const loader = this.getInternalLoader(context);
@@ -4764,47 +4706,7 @@ class LatencyController {
4764
4706
  this.currentTime = 0;
4765
4707
  this.stallCount = 0;
4766
4708
  this._latency = null;
4767
- this.onTimeupdate = () => {
4768
- const {
4769
- media,
4770
- levelDetails
4771
- } = this;
4772
- if (!media || !levelDetails) {
4773
- return;
4774
- }
4775
- this.currentTime = media.currentTime;
4776
- const latency = this.computeLatency();
4777
- if (latency === null) {
4778
- return;
4779
- }
4780
- this._latency = latency;
4781
-
4782
- // Adapt playbackRate to meet target latency in low-latency mode
4783
- const {
4784
- lowLatencyMode,
4785
- maxLiveSyncPlaybackRate
4786
- } = this.config;
4787
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4788
- return;
4789
- }
4790
- const targetLatency = this.targetLatency;
4791
- if (targetLatency === null) {
4792
- return;
4793
- }
4794
- const distanceFromTarget = latency - targetLatency;
4795
- // Only adjust playbackRate when within one target duration of targetLatency
4796
- // and more than one second from under-buffering.
4797
- // Playback further than one target duration from target can be considered DVR playback.
4798
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4799
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4800
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4801
- const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4802
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4803
- media.playbackRate = Math.min(max, Math.max(1, rate));
4804
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4805
- media.playbackRate = 1;
4806
- }
4807
- };
4709
+ this.timeupdateHandler = () => this.timeupdate();
4808
4710
  this.hls = hls;
4809
4711
  this.config = hls.config;
4810
4712
  this.registerListeners();
@@ -4896,7 +4798,7 @@ class LatencyController {
4896
4798
  this.onMediaDetaching();
4897
4799
  this.levelDetails = null;
4898
4800
  // @ts-ignore
4899
- this.hls = null;
4801
+ this.hls = this.timeupdateHandler = null;
4900
4802
  }
4901
4803
  registerListeners() {
4902
4804
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -4914,11 +4816,11 @@ class LatencyController {
4914
4816
  }
4915
4817
  onMediaAttached(event, data) {
4916
4818
  this.media = data.media;
4917
- this.media.addEventListener('timeupdate', this.onTimeupdate);
4819
+ this.media.addEventListener('timeupdate', this.timeupdateHandler);
4918
4820
  }
4919
4821
  onMediaDetaching() {
4920
4822
  if (this.media) {
4921
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4823
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4922
4824
  this.media = null;
4923
4825
  }
4924
4826
  }
@@ -4932,10 +4834,10 @@ class LatencyController {
4932
4834
  }) {
4933
4835
  this.levelDetails = details;
4934
4836
  if (details.advanced) {
4935
- this.onTimeupdate();
4837
+ this.timeupdate();
4936
4838
  }
4937
4839
  if (!details.live && this.media) {
4938
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4840
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4939
4841
  }
4940
4842
  }
4941
4843
  onError(event, data) {
@@ -4945,7 +4847,48 @@ class LatencyController {
4945
4847
  }
4946
4848
  this.stallCount++;
4947
4849
  if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
4948
- this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
4850
+ logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
4851
+ }
4852
+ }
4853
+ timeupdate() {
4854
+ const {
4855
+ media,
4856
+ levelDetails
4857
+ } = this;
4858
+ if (!media || !levelDetails) {
4859
+ return;
4860
+ }
4861
+ this.currentTime = media.currentTime;
4862
+ const latency = this.computeLatency();
4863
+ if (latency === null) {
4864
+ return;
4865
+ }
4866
+ this._latency = latency;
4867
+
4868
+ // Adapt playbackRate to meet target latency in low-latency mode
4869
+ const {
4870
+ lowLatencyMode,
4871
+ maxLiveSyncPlaybackRate
4872
+ } = this.config;
4873
+ if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4874
+ return;
4875
+ }
4876
+ const targetLatency = this.targetLatency;
4877
+ if (targetLatency === null) {
4878
+ return;
4879
+ }
4880
+ const distanceFromTarget = latency - targetLatency;
4881
+ // Only adjust playbackRate when within one target duration of targetLatency
4882
+ // and more than one second from under-buffering.
4883
+ // Playback further than one target duration from target can be considered DVR playback.
4884
+ const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4885
+ const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4886
+ if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4887
+ const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4888
+ const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4889
+ media.playbackRate = Math.min(max, Math.max(1, rate));
4890
+ } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4891
+ media.playbackRate = 1;
4949
4892
  }
4950
4893
  }
4951
4894
  estimateLiveEdge() {
@@ -5717,13 +5660,18 @@ var ErrorActionFlags = {
5717
5660
  MoveAllAlternatesMatchingHDCP: 2,
5718
5661
  SwitchToSDR: 4
5719
5662
  }; // Reserved for future use
5720
- class ErrorController extends Logger {
5663
+ class ErrorController {
5721
5664
  constructor(hls) {
5722
- super('error-controller', hls.logger);
5723
5665
  this.hls = void 0;
5724
5666
  this.playlistError = 0;
5725
5667
  this.penalizedRenditions = {};
5668
+ this.log = void 0;
5669
+ this.warn = void 0;
5670
+ this.error = void 0;
5726
5671
  this.hls = hls;
5672
+ this.log = logger.log.bind(logger, `[info]:`);
5673
+ this.warn = logger.warn.bind(logger, `[warning]:`);
5674
+ this.error = logger.error.bind(logger, `[error]:`);
5727
5675
  this.registerListeners();
5728
5676
  }
5729
5677
  registerListeners() {
@@ -6075,13 +6023,16 @@ class ErrorController extends Logger {
6075
6023
  }
6076
6024
  }
6077
6025
 
6078
- class BasePlaylistController extends Logger {
6026
+ class BasePlaylistController {
6079
6027
  constructor(hls, logPrefix) {
6080
- super(logPrefix, hls.logger);
6081
6028
  this.hls = void 0;
6082
6029
  this.timer = -1;
6083
6030
  this.requestScheduled = -1;
6084
6031
  this.canLoad = false;
6032
+ this.log = void 0;
6033
+ this.warn = void 0;
6034
+ this.log = logger.log.bind(logger, `${logPrefix}:`);
6035
+ this.warn = logger.warn.bind(logger, `${logPrefix}:`);
6085
6036
  this.hls = hls;
6086
6037
  }
6087
6038
  destroy() {
@@ -6114,7 +6065,7 @@ class BasePlaylistController extends Logger {
6114
6065
  try {
6115
6066
  uri = new self.URL(attr.URI, previous.url).href;
6116
6067
  } catch (error) {
6117
- this.warn(`Could not construct new URL for Rendition Report: ${error}`);
6068
+ logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
6118
6069
  uri = attr.URI || '';
6119
6070
  }
6120
6071
  // Use exact match. Otherwise, the last partial match, if any, will be used
@@ -6201,12 +6152,7 @@ class BasePlaylistController extends Logger {
6201
6152
  const cdnAge = lastAdvanced + details.ageHeader;
6202
6153
  let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
6203
6154
  if (currentGoal > 0) {
6204
- if (cdnAge > details.targetduration * 3) {
6205
- // Omit segment and part directives when the last response was more than 3 target durations ago,
6206
- this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
6207
- msn = undefined;
6208
- part = undefined;
6209
- } else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
6155
+ if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
6210
6156
  // If we attempted to get the next or latest playlist update, but currentGoal increased,
6211
6157
  // then we either can't catchup, or the "age" header cannot be trusted.
6212
6158
  this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
@@ -6878,9 +6824,8 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
6878
6824
  return -1;
6879
6825
  }
6880
6826
 
6881
- class AbrController extends Logger {
6827
+ class AbrController {
6882
6828
  constructor(_hls) {
6883
- super('abr', _hls.logger);
6884
6829
  this.hls = void 0;
6885
6830
  this.lastLevelLoadSec = 0;
6886
6831
  this.lastLoadedFragLevel = -1;
@@ -6994,7 +6939,7 @@ class AbrController extends Logger {
6994
6939
  this.resetEstimator(nextLoadLevelBitrate);
6995
6940
  }
6996
6941
  this.clearTimer();
6997
- this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6942
+ logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6998
6943
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
6999
6944
  Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
7000
6945
  Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
@@ -7014,7 +6959,7 @@ class AbrController extends Logger {
7014
6959
  }
7015
6960
  resetEstimator(abrEwmaDefaultEstimate) {
7016
6961
  if (abrEwmaDefaultEstimate) {
7017
- this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6962
+ logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
7018
6963
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
7019
6964
  }
7020
6965
  this.firstSelection = -1;
@@ -7246,7 +7191,7 @@ class AbrController extends Logger {
7246
7191
  }
7247
7192
  const firstLevel = this.hls.firstLevel;
7248
7193
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
7249
- this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7194
+ logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7250
7195
  return clamped;
7251
7196
  }
7252
7197
  get forcedAutoLevel() {
@@ -7292,9 +7237,6 @@ class AbrController extends Logger {
7292
7237
  partCurrent,
7293
7238
  hls
7294
7239
  } = this;
7295
- if (hls.levels.length <= 1) {
7296
- return hls.loadLevel;
7297
- }
7298
7240
  const {
7299
7241
  maxAutoLevel,
7300
7242
  config,
@@ -7327,13 +7269,13 @@ class AbrController extends Logger {
7327
7269
  // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
7328
7270
  const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
7329
7271
  maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
7330
- this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7272
+ logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7331
7273
  // don't use conservative factor on bitrate test
7332
7274
  bwFactor = bwUpFactor = 1;
7333
7275
  }
7334
7276
  }
7335
7277
  const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
7336
- this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7278
+ logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7337
7279
  if (bestLevel > -1) {
7338
7280
  return bestLevel;
7339
7281
  }
@@ -7407,7 +7349,7 @@ class AbrController extends Logger {
7407
7349
  currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
7408
7350
  currentFrameRate = minFramerate;
7409
7351
  currentBw = Math.max(currentBw, minBitrate);
7410
- this.log(`picked start tier ${JSON.stringify(startTier)}`);
7352
+ logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
7411
7353
  } else {
7412
7354
  currentCodecSet = level == null ? void 0 : level.codecSet;
7413
7355
  currentVideoRange = level == null ? void 0 : level.videoRange;
@@ -7434,11 +7376,11 @@ class AbrController extends Logger {
7434
7376
  const levels = this.hls.levels;
7435
7377
  const index = levels.indexOf(levelInfo);
7436
7378
  if (decodingInfo.error) {
7437
- this.warn(`MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7379
+ logger.warn(`[abr] MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7438
7380
  } else if (!decodingInfo.supported) {
7439
- this.warn(`Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7381
+ logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7440
7382
  if (index > -1 && levels.length > 1) {
7441
- this.log(`Removing unsupported level ${index}`);
7383
+ logger.log(`[abr] Removing unsupported level ${index}`);
7442
7384
  this.hls.removeLevel(index);
7443
7385
  }
7444
7386
  }
@@ -7485,9 +7427,9 @@ class AbrController extends Logger {
7485
7427
  const forcedAutoLevel = this.forcedAutoLevel;
7486
7428
  if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
7487
7429
  if (levelsSkipped.length) {
7488
- this.trace(`Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
7430
+ logger.trace(`[abr] Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
7489
7431
  }
7490
- this.info(`switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
7432
+ logger.info(`[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
7491
7433
  }
7492
7434
  if (firstSelection) {
7493
7435
  this.firstSelection = i;
@@ -7541,9 +7483,8 @@ class AbrController extends Logger {
7541
7483
  * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
7542
7484
  * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
7543
7485
  */
7544
- class TaskLoop extends Logger {
7545
- constructor(label, logger) {
7546
- super(label, logger);
7486
+ class TaskLoop {
7487
+ constructor() {
7547
7488
  this._boundTick = void 0;
7548
7489
  this._tickTimer = null;
7549
7490
  this._tickInterval = null;
@@ -7705,16 +7646,13 @@ class FragmentTracker {
7705
7646
  * If not found any Fragment, return null
7706
7647
  */
7707
7648
  getBufferedFrag(position, levelType) {
7708
- return this.getFragAtPos(position, levelType, true);
7709
- }
7710
- getFragAtPos(position, levelType, buffered) {
7711
7649
  const {
7712
7650
  fragments
7713
7651
  } = this;
7714
7652
  const keys = Object.keys(fragments);
7715
7653
  for (let i = keys.length; i--;) {
7716
7654
  const fragmentEntity = fragments[keys[i]];
7717
- if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && (!buffered || fragmentEntity.buffered)) {
7655
+ if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && fragmentEntity.buffered) {
7718
7656
  const frag = fragmentEntity.body;
7719
7657
  if (frag.start <= position && position <= frag.end) {
7720
7658
  return frag;
@@ -7969,8 +7907,7 @@ class FragmentTracker {
7969
7907
  const {
7970
7908
  frag,
7971
7909
  part,
7972
- timeRanges,
7973
- type
7910
+ timeRanges
7974
7911
  } = data;
7975
7912
  if (frag.sn === 'initSegment') {
7976
7913
  return;
@@ -7985,8 +7922,10 @@ class FragmentTracker {
7985
7922
  }
7986
7923
  // Store the latest timeRanges loaded in the buffer
7987
7924
  this.timeRanges = timeRanges;
7988
- const timeRange = timeRanges[type];
7989
- this.detectEvictedFragments(type, timeRange, playlistType, part);
7925
+ Object.keys(timeRanges).forEach(elementaryStream => {
7926
+ const timeRange = timeRanges[elementaryStream];
7927
+ this.detectEvictedFragments(elementaryStream, timeRange, playlistType, part);
7928
+ });
7990
7929
  }
7991
7930
  onFragBuffered(event, data) {
7992
7931
  this.detectPartialFragments(data);
@@ -8064,29 +8003,40 @@ class BufferHelper {
8064
8003
  * Return true if `media`'s buffered include `position`
8065
8004
  */
8066
8005
  static isBuffered(media, position) {
8067
- if (media) {
8068
- const buffered = BufferHelper.getBuffered(media);
8069
- for (let i = buffered.length; i--;) {
8070
- if (position >= buffered.start(i) && position <= buffered.end(i)) {
8071
- return true;
8006
+ try {
8007
+ if (media) {
8008
+ const buffered = BufferHelper.getBuffered(media);
8009
+ for (let i = 0; i < buffered.length; i++) {
8010
+ if (position >= buffered.start(i) && position <= buffered.end(i)) {
8011
+ return true;
8012
+ }
8072
8013
  }
8073
8014
  }
8015
+ } catch (error) {
8016
+ // this is to catch
8017
+ // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
8018
+ // This SourceBuffer has been removed from the parent media source
8074
8019
  }
8075
8020
  return false;
8076
8021
  }
8077
8022
  static bufferInfo(media, pos, maxHoleDuration) {
8078
- if (media) {
8079
- const vbuffered = BufferHelper.getBuffered(media);
8080
- if (vbuffered.length) {
8023
+ try {
8024
+ if (media) {
8025
+ const vbuffered = BufferHelper.getBuffered(media);
8081
8026
  const buffered = [];
8082
- for (let i = 0; i < vbuffered.length; i++) {
8027
+ let i;
8028
+ for (i = 0; i < vbuffered.length; i++) {
8083
8029
  buffered.push({
8084
8030
  start: vbuffered.start(i),
8085
8031
  end: vbuffered.end(i)
8086
8032
  });
8087
8033
  }
8088
- return BufferHelper.bufferedInfo(buffered, pos, maxHoleDuration);
8034
+ return this.bufferedInfo(buffered, pos, maxHoleDuration);
8089
8035
  }
8036
+ } catch (error) {
8037
+ // this is to catch
8038
+ // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
8039
+ // This SourceBuffer has been removed from the parent media source
8090
8040
  }
8091
8041
  return {
8092
8042
  len: 0,
@@ -8098,7 +8048,14 @@ class BufferHelper {
8098
8048
  static bufferedInfo(buffered, pos, maxHoleDuration) {
8099
8049
  pos = Math.max(0, pos);
8100
8050
  // sort on buffer.start/smaller end (IE does not always return sorted buffered range)
8101
- buffered.sort((a, b) => a.start - b.start || b.end - a.end);
8051
+ buffered.sort(function (a, b) {
8052
+ const diff = a.start - b.start;
8053
+ if (diff) {
8054
+ return diff;
8055
+ } else {
8056
+ return b.end - a.end;
8057
+ }
8058
+ });
8102
8059
  let buffered2 = [];
8103
8060
  if (maxHoleDuration) {
8104
8061
  // there might be some small holes between buffer time range
@@ -8165,7 +8122,7 @@ class BufferHelper {
8165
8122
  */
8166
8123
  static getBuffered(media) {
8167
8124
  try {
8168
- return media.buffered || noopBuffered;
8125
+ return media.buffered;
8169
8126
  } catch (e) {
8170
8127
  logger.log('failed to get media.buffered', e);
8171
8128
  return noopBuffered;
@@ -8618,8 +8575,8 @@ function createLoaderContext(frag, part = null) {
8618
8575
  var _frag$decryptdata;
8619
8576
  let byteRangeStart = start;
8620
8577
  let byteRangeEnd = end;
8621
- if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
8622
- // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
8578
+ if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
8579
+ // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
8623
8580
  // has the unencrypted size specified in the range.
8624
8581
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
8625
8582
  const fragmentLen = end - start;
@@ -8652,9 +8609,6 @@ function createGapLoadError(frag, part) {
8652
8609
  (part ? part : frag).stats.aborted = true;
8653
8610
  return new LoadError(errorData);
8654
8611
  }
8655
- function isMethodFullSegmentAesCbc(method) {
8656
- return method === 'AES-128' || method === 'AES-256';
8657
- }
8658
8612
  class LoadError extends Error {
8659
8613
  constructor(data) {
8660
8614
  super(data.error.message);
@@ -8664,61 +8618,33 @@ class LoadError extends Error {
8664
8618
  }
8665
8619
 
8666
8620
  class AESCrypto {
8667
- constructor(subtle, iv, aesMode) {
8621
+ constructor(subtle, iv) {
8668
8622
  this.subtle = void 0;
8669
8623
  this.aesIV = void 0;
8670
- this.aesMode = void 0;
8671
8624
  this.subtle = subtle;
8672
8625
  this.aesIV = iv;
8673
- this.aesMode = aesMode;
8674
8626
  }
8675
8627
  decrypt(data, key) {
8676
- switch (this.aesMode) {
8677
- case DecrypterAesMode.cbc:
8678
- return this.subtle.decrypt({
8679
- name: 'AES-CBC',
8680
- iv: this.aesIV
8681
- }, key, data);
8682
- case DecrypterAesMode.ctr:
8683
- return this.subtle.decrypt({
8684
- name: 'AES-CTR',
8685
- counter: this.aesIV,
8686
- length: 64
8687
- },
8688
- //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
8689
- key, data);
8690
- default:
8691
- throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
8692
- }
8628
+ return this.subtle.decrypt({
8629
+ name: 'AES-CBC',
8630
+ iv: this.aesIV
8631
+ }, key, data);
8693
8632
  }
8694
8633
  }
8695
8634
 
8696
8635
  class FastAESKey {
8697
- constructor(subtle, key, aesMode) {
8636
+ constructor(subtle, key) {
8698
8637
  this.subtle = void 0;
8699
8638
  this.key = void 0;
8700
- this.aesMode = void 0;
8701
8639
  this.subtle = subtle;
8702
8640
  this.key = key;
8703
- this.aesMode = aesMode;
8704
8641
  }
8705
8642
  expandKey() {
8706
- const subtleAlgoName = getSubtleAlgoName(this.aesMode);
8707
8643
  return this.subtle.importKey('raw', this.key, {
8708
- name: subtleAlgoName
8644
+ name: 'AES-CBC'
8709
8645
  }, false, ['encrypt', 'decrypt']);
8710
8646
  }
8711
8647
  }
8712
- function getSubtleAlgoName(aesMode) {
8713
- switch (aesMode) {
8714
- case DecrypterAesMode.cbc:
8715
- return 'AES-CBC';
8716
- case DecrypterAesMode.ctr:
8717
- return 'AES-CTR';
8718
- default:
8719
- throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
8720
- }
8721
- }
8722
8648
 
8723
8649
  // PKCS7
8724
8650
  function removePadding(array) {
@@ -8968,8 +8894,7 @@ class Decrypter {
8968
8894
  this.currentIV = null;
8969
8895
  this.currentResult = null;
8970
8896
  this.useSoftware = void 0;
8971
- this.enableSoftwareAES = void 0;
8972
- this.enableSoftwareAES = config.enableSoftwareAES;
8897
+ this.useSoftware = config.enableSoftwareAES;
8973
8898
  this.removePKCS7Padding = removePKCS7Padding;
8974
8899
  // built in decryptor expects PKCS7 padding
8975
8900
  if (removePKCS7Padding) {
@@ -8982,7 +8907,9 @@ class Decrypter {
8982
8907
  /* no-op */
8983
8908
  }
8984
8909
  }
8985
- this.useSoftware = this.subtle === null;
8910
+ if (this.subtle === null) {
8911
+ this.useSoftware = true;
8912
+ }
8986
8913
  }
8987
8914
  destroy() {
8988
8915
  this.subtle = null;
@@ -9020,10 +8947,10 @@ class Decrypter {
9020
8947
  this.softwareDecrypter = null;
9021
8948
  }
9022
8949
  }
9023
- decrypt(data, key, iv, aesMode) {
8950
+ decrypt(data, key, iv) {
9024
8951
  if (this.useSoftware) {
9025
8952
  return new Promise((resolve, reject) => {
9026
- this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
8953
+ this.softwareDecrypt(new Uint8Array(data), key, iv);
9027
8954
  const decryptResult = this.flush();
9028
8955
  if (decryptResult) {
9029
8956
  resolve(decryptResult.buffer);
@@ -9032,21 +8959,17 @@ class Decrypter {
9032
8959
  }
9033
8960
  });
9034
8961
  }
9035
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
8962
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
9036
8963
  }
9037
8964
 
9038
8965
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
9039
8966
  // data is handled in the flush() call
9040
- softwareDecrypt(data, key, iv, aesMode) {
8967
+ softwareDecrypt(data, key, iv) {
9041
8968
  const {
9042
8969
  currentIV,
9043
8970
  currentResult,
9044
8971
  remainderData
9045
8972
  } = this;
9046
- if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
9047
- logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
9048
- return null;
9049
- }
9050
8973
  this.logOnce('JS AES decrypt');
9051
8974
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
9052
8975
  // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
@@ -9079,11 +9002,11 @@ class Decrypter {
9079
9002
  }
9080
9003
  return result;
9081
9004
  }
9082
- webCryptoDecrypt(data, key, iv, aesMode) {
9005
+ webCryptoDecrypt(data, key, iv) {
9083
9006
  const subtle = this.subtle;
9084
9007
  if (this.key !== key || !this.fastAesKey) {
9085
9008
  this.key = key;
9086
- this.fastAesKey = new FastAESKey(subtle, key, aesMode);
9009
+ this.fastAesKey = new FastAESKey(subtle, key);
9087
9010
  }
9088
9011
  return this.fastAesKey.expandKey().then(aesKey => {
9089
9012
  // decrypt using web crypto
@@ -9091,25 +9014,22 @@ class Decrypter {
9091
9014
  return Promise.reject(new Error('web crypto not initialized'));
9092
9015
  }
9093
9016
  this.logOnce('WebCrypto AES decrypt');
9094
- const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
9017
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv));
9095
9018
  return crypto.decrypt(data.buffer, aesKey);
9096
9019
  }).catch(err => {
9097
9020
  logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
9098
- return this.onWebCryptoError(data, key, iv, aesMode);
9021
+ return this.onWebCryptoError(data, key, iv);
9099
9022
  });
9100
9023
  }
9101
- onWebCryptoError(data, key, iv, aesMode) {
9102
- const enableSoftwareAES = this.enableSoftwareAES;
9103
- if (enableSoftwareAES) {
9104
- this.useSoftware = true;
9105
- this.logEnabled = true;
9106
- this.softwareDecrypt(data, key, iv, aesMode);
9107
- const decryptResult = this.flush();
9108
- if (decryptResult) {
9109
- return decryptResult.buffer;
9110
- }
9024
+ onWebCryptoError(data, key, iv) {
9025
+ this.useSoftware = true;
9026
+ this.logEnabled = true;
9027
+ this.softwareDecrypt(data, key, iv);
9028
+ const decryptResult = this.flush();
9029
+ if (decryptResult) {
9030
+ return decryptResult.buffer;
9111
9031
  }
9112
- throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
9032
+ throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
9113
9033
  }
9114
9034
  getValidChunk(data) {
9115
9035
  let currentChunk = data;
@@ -9160,7 +9080,7 @@ const State = {
9160
9080
  };
9161
9081
  class BaseStreamController extends TaskLoop {
9162
9082
  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
9163
- super(logPrefix, hls.logger);
9083
+ super();
9164
9084
  this.hls = void 0;
9165
9085
  this.fragPrevious = null;
9166
9086
  this.fragCurrent = null;
@@ -9185,98 +9105,22 @@ class BaseStreamController extends TaskLoop {
9185
9105
  this.startFragRequested = false;
9186
9106
  this.decrypter = void 0;
9187
9107
  this.initPTS = [];
9188
- this.buffering = true;
9189
- this.loadingParts = false;
9190
- this.onMediaSeeking = () => {
9191
- const {
9192
- config,
9193
- fragCurrent,
9194
- media,
9195
- mediaBuffer,
9196
- state
9197
- } = this;
9198
- const currentTime = media ? media.currentTime : 0;
9199
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9200
- this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9201
- if (this.state === State.ENDED) {
9202
- this.resetLoadingState();
9203
- } else if (fragCurrent) {
9204
- // Seeking while frag load is in progress
9205
- const tolerance = config.maxFragLookUpTolerance;
9206
- const fragStartOffset = fragCurrent.start - tolerance;
9207
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9208
- // if seeking out of buffered range or into new one
9209
- if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9210
- const pastFragment = currentTime > fragEndOffset;
9211
- // if the seek position is outside the current fragment range
9212
- if (currentTime < fragStartOffset || pastFragment) {
9213
- if (pastFragment && fragCurrent.loader) {
9214
- this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9215
- fragCurrent.abortRequests();
9216
- this.resetLoadingState();
9217
- }
9218
- this.fragPrevious = null;
9219
- }
9220
- }
9221
- }
9222
- if (media) {
9223
- // Remove gap fragments
9224
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9225
- this.lastCurrentTime = currentTime;
9226
- if (!this.loadingParts) {
9227
- const bufferEnd = Math.max(bufferInfo.end, currentTime);
9228
- const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
9229
- if (shouldLoadParts) {
9230
- this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
9231
- this.loadingParts = shouldLoadParts;
9232
- }
9233
- }
9234
- }
9235
-
9236
- // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9237
- if (!this.loadedmetadata && !bufferInfo.len) {
9238
- this.nextLoadPosition = this.startPosition = currentTime;
9239
- }
9240
-
9241
- // Async tick to speed up processing
9242
- this.tickImmediate();
9243
- };
9244
- this.onMediaEnded = () => {
9245
- // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9246
- this.startPosition = this.lastCurrentTime = 0;
9247
- if (this.playlistType === PlaylistLevelType.MAIN) {
9248
- this.hls.trigger(Events.MEDIA_ENDED, {
9249
- stalled: false
9250
- });
9251
- }
9252
- };
9108
+ this.onvseeking = null;
9109
+ this.onvended = null;
9110
+ this.logPrefix = '';
9111
+ this.log = void 0;
9112
+ this.warn = void 0;
9253
9113
  this.playlistType = playlistType;
9114
+ this.logPrefix = logPrefix;
9115
+ this.log = logger.log.bind(logger, `${logPrefix}:`);
9116
+ this.warn = logger.warn.bind(logger, `${logPrefix}:`);
9254
9117
  this.hls = hls;
9255
9118
  this.fragmentLoader = new FragmentLoader(hls.config);
9256
9119
  this.keyLoader = keyLoader;
9257
9120
  this.fragmentTracker = fragmentTracker;
9258
9121
  this.config = hls.config;
9259
9122
  this.decrypter = new Decrypter(hls.config);
9260
- }
9261
- registerListeners() {
9262
- const {
9263
- hls
9264
- } = this;
9265
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
9266
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
9267
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
9268
9123
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9269
- hls.on(Events.ERROR, this.onError, this);
9270
- }
9271
- unregisterListeners() {
9272
- const {
9273
- hls
9274
- } = this;
9275
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
9276
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
9277
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
9278
- hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9279
- hls.off(Events.ERROR, this.onError, this);
9280
9124
  }
9281
9125
  doTick() {
9282
9126
  this.onTickEnd();
@@ -9300,12 +9144,6 @@ class BaseStreamController extends TaskLoop {
9300
9144
  this.clearNextTick();
9301
9145
  this.state = State.STOPPED;
9302
9146
  }
9303
- pauseBuffering() {
9304
- this.buffering = false;
9305
- }
9306
- resumeBuffering() {
9307
- this.buffering = true;
9308
- }
9309
9147
  _streamEnded(bufferInfo, levelDetails) {
9310
9148
  // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
9311
9149
  // of nothing loading/loaded return false
@@ -9336,8 +9174,10 @@ class BaseStreamController extends TaskLoop {
9336
9174
  }
9337
9175
  onMediaAttached(event, data) {
9338
9176
  const media = this.media = this.mediaBuffer = data.media;
9339
- media.addEventListener('seeking', this.onMediaSeeking);
9340
- media.addEventListener('ended', this.onMediaEnded);
9177
+ this.onvseeking = this.onMediaSeeking.bind(this);
9178
+ this.onvended = this.onMediaEnded.bind(this);
9179
+ media.addEventListener('seeking', this.onvseeking);
9180
+ media.addEventListener('ended', this.onvended);
9341
9181
  const config = this.config;
9342
9182
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
9343
9183
  this.startLoad(config.startPosition);
@@ -9351,9 +9191,10 @@ class BaseStreamController extends TaskLoop {
9351
9191
  }
9352
9192
 
9353
9193
  // remove video listeners
9354
- if (media) {
9355
- media.removeEventListener('seeking', this.onMediaSeeking);
9356
- media.removeEventListener('ended', this.onMediaEnded);
9194
+ if (media && this.onvseeking && this.onvended) {
9195
+ media.removeEventListener('seeking', this.onvseeking);
9196
+ media.removeEventListener('ended', this.onvended);
9197
+ this.onvseeking = this.onvended = null;
9357
9198
  }
9358
9199
  if (this.keyLoader) {
9359
9200
  this.keyLoader.detach();
@@ -9363,8 +9204,56 @@ class BaseStreamController extends TaskLoop {
9363
9204
  this.fragmentTracker.removeAllFragments();
9364
9205
  this.stopLoad();
9365
9206
  }
9366
- onManifestLoading() {}
9367
- onError(event, data) {}
9207
+ onMediaSeeking() {
9208
+ const {
9209
+ config,
9210
+ fragCurrent,
9211
+ media,
9212
+ mediaBuffer,
9213
+ state
9214
+ } = this;
9215
+ const currentTime = media ? media.currentTime : 0;
9216
+ const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9217
+ this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9218
+ if (this.state === State.ENDED) {
9219
+ this.resetLoadingState();
9220
+ } else if (fragCurrent) {
9221
+ // Seeking while frag load is in progress
9222
+ const tolerance = config.maxFragLookUpTolerance;
9223
+ const fragStartOffset = fragCurrent.start - tolerance;
9224
+ const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9225
+ // if seeking out of buffered range or into new one
9226
+ if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9227
+ const pastFragment = currentTime > fragEndOffset;
9228
+ // if the seek position is outside the current fragment range
9229
+ if (currentTime < fragStartOffset || pastFragment) {
9230
+ if (pastFragment && fragCurrent.loader) {
9231
+ this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9232
+ fragCurrent.abortRequests();
9233
+ this.resetLoadingState();
9234
+ }
9235
+ this.fragPrevious = null;
9236
+ }
9237
+ }
9238
+ }
9239
+ if (media) {
9240
+ // Remove gap fragments
9241
+ this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9242
+ this.lastCurrentTime = currentTime;
9243
+ }
9244
+
9245
+ // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9246
+ if (!this.loadedmetadata && !bufferInfo.len) {
9247
+ this.nextLoadPosition = this.startPosition = currentTime;
9248
+ }
9249
+
9250
+ // Async tick to speed up processing
9251
+ this.tickImmediate();
9252
+ }
9253
+ onMediaEnded() {
9254
+ // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9255
+ this.startPosition = this.lastCurrentTime = 0;
9256
+ }
9368
9257
  onManifestLoaded(event, data) {
9369
9258
  this.startTimeOffset = data.startTimeOffset;
9370
9259
  this.initPTS = [];
@@ -9374,7 +9263,7 @@ class BaseStreamController extends TaskLoop {
9374
9263
  this.stopLoad();
9375
9264
  super.onHandlerDestroying();
9376
9265
  // @ts-ignore
9377
- this.hls = this.onMediaSeeking = this.onMediaEnded = null;
9266
+ this.hls = null;
9378
9267
  }
9379
9268
  onHandlerDestroyed() {
9380
9269
  this.state = State.STOPPED;
@@ -9505,10 +9394,10 @@ class BaseStreamController extends TaskLoop {
9505
9394
  const decryptData = frag.decryptdata;
9506
9395
 
9507
9396
  // check to see if the payload needs to be decrypted
9508
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
9397
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
9509
9398
  const startTime = self.performance.now();
9510
9399
  // decrypt init segment data
9511
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
9400
+ return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
9512
9401
  hls.trigger(Events.ERROR, {
9513
9402
  type: ErrorTypes.MEDIA_ERROR,
9514
9403
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -9620,7 +9509,7 @@ class BaseStreamController extends TaskLoop {
9620
9509
  }
9621
9510
  let keyLoadingPromise = null;
9622
9511
  if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
9623
- this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
9512
+ this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
9624
9513
  this.state = State.KEY_LOADING;
9625
9514
  this.fragCurrent = frag;
9626
9515
  keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
@@ -9641,16 +9530,8 @@ class BaseStreamController extends TaskLoop {
9641
9530
  } else if (!frag.encrypted && details.encryptedFragments.length) {
9642
9531
  this.keyLoader.loadClear(frag, details.encryptedFragments);
9643
9532
  }
9644
- const fragPrevious = this.fragPrevious;
9645
- if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
9646
- const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
9647
- if (shouldLoadParts !== this.loadingParts) {
9648
- this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
9649
- this.loadingParts = shouldLoadParts;
9650
- }
9651
- }
9652
9533
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
9653
- if (this.loadingParts && frag.sn !== 'initSegment') {
9534
+ if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
9654
9535
  const partList = details.partList;
9655
9536
  if (partList && progressCallback) {
9656
9537
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -9659,7 +9540,7 @@ class BaseStreamController extends TaskLoop {
9659
9540
  const partIndex = this.getNextPart(partList, frag, targetBufferTime);
9660
9541
  if (partIndex > -1) {
9661
9542
  const part = partList[partIndex];
9662
- this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
9543
+ this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
9663
9544
  this.nextLoadPosition = part.start + part.duration;
9664
9545
  this.state = State.FRAG_LOADING;
9665
9546
  let _result;
@@ -9688,14 +9569,7 @@ class BaseStreamController extends TaskLoop {
9688
9569
  }
9689
9570
  }
9690
9571
  }
9691
- if (frag.sn !== 'initSegment' && this.loadingParts) {
9692
- this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
9693
- this.loadingParts = false;
9694
- } else if (!frag.url) {
9695
- // Selected fragment hint for part but not loading parts
9696
- return Promise.resolve(null);
9697
- }
9698
- this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
9572
+ this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
9699
9573
  // Don't update nextLoadPosition for fragments which are not buffered
9700
9574
  if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
9701
9575
  this.nextLoadPosition = frag.start + frag.duration;
@@ -9793,36 +9667,8 @@ class BaseStreamController extends TaskLoop {
9793
9667
  if (part) {
9794
9668
  part.stats.parsing.end = now;
9795
9669
  }
9796
- // See if part loading should be disabled/enabled based on buffer and playback position.
9797
- if (frag.sn !== 'initSegment') {
9798
- const levelDetails = this.getLevelDetails();
9799
- const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
9800
- const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
9801
- if (shouldLoadParts !== this.loadingParts) {
9802
- this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
9803
- this.loadingParts = shouldLoadParts;
9804
- }
9805
- }
9806
9670
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
9807
9671
  }
9808
- shouldLoadParts(details, bufferEnd) {
9809
- if (this.config.lowLatencyMode) {
9810
- if (!details) {
9811
- return this.loadingParts;
9812
- }
9813
- if (details != null && details.partList) {
9814
- var _details$fragmentHint;
9815
- // Buffer must be ahead of first part + duration of parts after last segment
9816
- // and playback must be at or past segment adjacent to part list
9817
- const firstPart = details.partList[0];
9818
- const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
9819
- if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
9820
- return true;
9821
- }
9822
- }
9823
- }
9824
- return false;
9825
- }
9826
9672
  getCurrentContext(chunkMeta) {
9827
9673
  const {
9828
9674
  levels,
@@ -9923,7 +9769,7 @@ class BaseStreamController extends TaskLoop {
9923
9769
  // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
9924
9770
  if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
9925
9771
  const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
9926
- if (bufferedFragAtPos && (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)) {
9772
+ if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
9927
9773
  return BufferHelper.bufferInfo(bufferable, pos, Math.max(bufferInfo.nextStart, maxBufferHole));
9928
9774
  }
9929
9775
  }
@@ -9971,8 +9817,7 @@ class BaseStreamController extends TaskLoop {
9971
9817
  config
9972
9818
  } = this;
9973
9819
  const start = fragments[0].start;
9974
- const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
9975
- let frag = null;
9820
+ let frag;
9976
9821
  if (levelDetails.live) {
9977
9822
  const initialLiveManifestSize = config.initialLiveManifestSize;
9978
9823
  if (fragLen < initialLiveManifestSize) {
@@ -9984,10 +9829,6 @@ class BaseStreamController extends TaskLoop {
9984
9829
  // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
9985
9830
  // we get the fragment matching that start time
9986
9831
  if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
9987
- if (canLoadParts && !this.loadingParts) {
9988
- this.log(`LL-Part loading ON for initial live fragment`);
9989
- this.loadingParts = true;
9990
- }
9991
9832
  frag = this.getInitialLiveFragment(levelDetails, fragments);
9992
9833
  this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
9993
9834
  }
@@ -9998,7 +9839,7 @@ class BaseStreamController extends TaskLoop {
9998
9839
 
9999
9840
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
10000
9841
  if (!frag) {
10001
- const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
9842
+ const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
10002
9843
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
10003
9844
  }
10004
9845
  return this.mapToInitFragWhenRequired(frag);
@@ -10120,7 +9961,7 @@ class BaseStreamController extends TaskLoop {
10120
9961
  } = levelDetails;
10121
9962
  const tolerance = config.maxFragLookUpTolerance;
10122
9963
  const partList = levelDetails.partList;
10123
- const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
9964
+ const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
10124
9965
  if (loadingParts && fragmentHint && !this.bitrateTest) {
10125
9966
  // Include incomplete fragment with parts at end
10126
9967
  fragments = fragments.concat(fragmentHint);
@@ -10313,7 +10154,7 @@ class BaseStreamController extends TaskLoop {
10313
10154
  errorAction.resolved = true;
10314
10155
  }
10315
10156
  } else {
10316
- this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10157
+ logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10317
10158
  return;
10318
10159
  }
10319
10160
  } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
@@ -10381,9 +10222,7 @@ class BaseStreamController extends TaskLoop {
10381
10222
  this.log('Reset loading state');
10382
10223
  this.fragCurrent = null;
10383
10224
  this.fragPrevious = null;
10384
- if (this.state !== State.STOPPED) {
10385
- this.state = State.IDLE;
10386
- }
10225
+ this.state = State.IDLE;
10387
10226
  }
10388
10227
  resetStartWhenNotLoaded(level) {
10389
10228
  // if loadedmetadata is not set, it means that first frag request failed
@@ -10724,7 +10563,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
10724
10563
  */
10725
10564
  function getAudioConfig(observer, data, offset, audioCodec) {
10726
10565
  let adtsObjectType;
10727
- let originalAdtsObjectType;
10728
10566
  let adtsExtensionSamplingIndex;
10729
10567
  let adtsChannelConfig;
10730
10568
  let config;
@@ -10732,7 +10570,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10732
10570
  const manifestCodec = audioCodec;
10733
10571
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
10734
10572
  // byte 2
10735
- adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10573
+ adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10736
10574
  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
10737
10575
  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
10738
10576
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
@@ -10749,8 +10587,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10749
10587
  // byte 3
10750
10588
  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
10751
10589
  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
10752
- // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
10753
- if (/firefox|palemoon/i.test(userAgent)) {
10590
+ // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
10591
+ if (/firefox/i.test(userAgent)) {
10754
10592
  if (adtsSamplingIndex >= 6) {
10755
10593
  adtsObjectType = 5;
10756
10594
  config = new Array(4);
@@ -10844,7 +10682,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10844
10682
  samplerate: adtsSamplingRates[adtsSamplingIndex],
10845
10683
  channelCount: adtsChannelConfig,
10846
10684
  codec: 'mp4a.40.' + adtsObjectType,
10847
- parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
10848
10685
  manifestCodec
10849
10686
  };
10850
10687
  }
@@ -10899,8 +10736,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
10899
10736
  track.channelCount = config.channelCount;
10900
10737
  track.codec = config.codec;
10901
10738
  track.manifestCodec = config.manifestCodec;
10902
- track.parsedCodec = config.parsedCodec;
10903
- logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10739
+ logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10904
10740
  }
10905
10741
  }
10906
10742
  function getFrameDuration(samplerate) {
@@ -11491,110 +11327,6 @@ class BaseVideoParser {
11491
11327
  logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
11492
11328
  }
11493
11329
  }
11494
- parseNALu(track, array) {
11495
- const len = array.byteLength;
11496
- let state = track.naluState || 0;
11497
- const lastState = state;
11498
- const units = [];
11499
- let i = 0;
11500
- let value;
11501
- let overflow;
11502
- let unitType;
11503
- let lastUnitStart = -1;
11504
- let lastUnitType = 0;
11505
- // logger.log('PES:' + Hex.hexDump(array));
11506
-
11507
- if (state === -1) {
11508
- // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
11509
- lastUnitStart = 0;
11510
- // NALu type is value read from offset 0
11511
- lastUnitType = this.getNALuType(array, 0);
11512
- state = 0;
11513
- i = 1;
11514
- }
11515
- while (i < len) {
11516
- value = array[i++];
11517
- // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
11518
- if (!state) {
11519
- state = value ? 0 : 1;
11520
- continue;
11521
- }
11522
- if (state === 1) {
11523
- state = value ? 0 : 2;
11524
- continue;
11525
- }
11526
- // here we have state either equal to 2 or 3
11527
- if (!value) {
11528
- state = 3;
11529
- } else if (value === 1) {
11530
- overflow = i - state - 1;
11531
- if (lastUnitStart >= 0) {
11532
- const unit = {
11533
- data: array.subarray(lastUnitStart, overflow),
11534
- type: lastUnitType
11535
- };
11536
- // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
11537
- units.push(unit);
11538
- } else {
11539
- // lastUnitStart is undefined => this is the first start code found in this PES packet
11540
- // first check if start code delimiter is overlapping between 2 PES packets,
11541
- // ie it started in last packet (lastState not zero)
11542
- // and ended at the beginning of this PES packet (i <= 4 - lastState)
11543
- const lastUnit = this.getLastNalUnit(track.samples);
11544
- if (lastUnit) {
11545
- if (lastState && i <= 4 - lastState) {
11546
- // start delimiter overlapping between PES packets
11547
- // strip start delimiter bytes from the end of last NAL unit
11548
- // check if lastUnit had a state different from zero
11549
- if (lastUnit.state) {
11550
- // strip last bytes
11551
- lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
11552
- }
11553
- }
11554
- // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
11555
-
11556
- if (overflow > 0) {
11557
- // logger.log('first NALU found with overflow:' + overflow);
11558
- lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
11559
- lastUnit.state = 0;
11560
- }
11561
- }
11562
- }
11563
- // check if we can read unit type
11564
- if (i < len) {
11565
- unitType = this.getNALuType(array, i);
11566
- // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
11567
- lastUnitStart = i;
11568
- lastUnitType = unitType;
11569
- state = 0;
11570
- } else {
11571
- // not enough byte to read unit type. let's read it on next PES parsing
11572
- state = -1;
11573
- }
11574
- } else {
11575
- state = 0;
11576
- }
11577
- }
11578
- if (lastUnitStart >= 0 && state >= 0) {
11579
- const unit = {
11580
- data: array.subarray(lastUnitStart, len),
11581
- type: lastUnitType,
11582
- state: state
11583
- };
11584
- units.push(unit);
11585
- // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
11586
- }
11587
- // no NALu found
11588
- if (units.length === 0) {
11589
- // append pes.data to previous NAL unit
11590
- const lastUnit = this.getLastNalUnit(track.samples);
11591
- if (lastUnit) {
11592
- lastUnit.data = appendUint8Array(lastUnit.data, array);
11593
- }
11594
- }
11595
- track.naluState = state;
11596
- return units;
11597
- }
11598
11330
  }
11599
11331
 
11600
11332
  /**
@@ -11737,171 +11469,21 @@ class ExpGolomb {
11737
11469
  readUInt() {
11738
11470
  return this.readBits(32);
11739
11471
  }
11740
- }
11741
-
11742
- class AvcVideoParser extends BaseVideoParser {
11743
- parsePES(track, textTrack, pes, last, duration) {
11744
- const units = this.parseNALu(track, pes.data);
11745
- let VideoSample = this.VideoSample;
11746
- let push;
11747
- let spsfound = false;
11748
- // free pes.data to save up some memory
11749
- pes.data = null;
11750
-
11751
- // if new NAL units found and last sample still there, let's push ...
11752
- // this helps parsing streams with missing AUD (only do this if AUD never found)
11753
- if (VideoSample && units.length && !track.audFound) {
11754
- this.pushAccessUnit(VideoSample, track);
11755
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
11756
- }
11757
- units.forEach(unit => {
11758
- var _VideoSample2;
11759
- switch (unit.type) {
11760
- // NDR
11761
- case 1:
11762
- {
11763
- let iskey = false;
11764
- push = true;
11765
- const data = unit.data;
11766
- // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
11767
- if (spsfound && data.length > 4) {
11768
- // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
11769
- const sliceType = this.readSliceType(data);
11770
- // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
11771
- // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
11772
- // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
11773
- // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
11774
- // if (sliceType === 2 || sliceType === 7) {
11775
- if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
11776
- iskey = true;
11777
- }
11778
- }
11779
- if (iskey) {
11780
- var _VideoSample;
11781
- // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
11782
- if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
11783
- this.pushAccessUnit(VideoSample, track);
11784
- VideoSample = this.VideoSample = null;
11785
- }
11786
- }
11787
- if (!VideoSample) {
11788
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11789
- }
11790
- VideoSample.frame = true;
11791
- VideoSample.key = iskey;
11792
- break;
11793
- // IDR
11794
- }
11795
- case 5:
11796
- push = true;
11797
- // handle PES not starting with AUD
11798
- // if we have frame data already, that cannot belong to the same frame, so force a push
11799
- if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
11800
- this.pushAccessUnit(VideoSample, track);
11801
- VideoSample = this.VideoSample = null;
11802
- }
11803
- if (!VideoSample) {
11804
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11805
- }
11806
- VideoSample.key = true;
11807
- VideoSample.frame = true;
11808
- break;
11809
- // SEI
11810
- case 6:
11811
- {
11812
- push = true;
11813
- parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
11814
- break;
11815
- // SPS
11816
- }
11817
- case 7:
11818
- {
11819
- var _track$pixelRatio, _track$pixelRatio2;
11820
- push = true;
11821
- spsfound = true;
11822
- const sps = unit.data;
11823
- const config = this.readSPS(sps);
11824
- if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) {
11825
- track.width = config.width;
11826
- track.height = config.height;
11827
- track.pixelRatio = config.pixelRatio;
11828
- track.sps = [sps];
11829
- track.duration = duration;
11830
- const codecarray = sps.subarray(1, 4);
11831
- let codecstring = 'avc1.';
11832
- for (let i = 0; i < 3; i++) {
11833
- let h = codecarray[i].toString(16);
11834
- if (h.length < 2) {
11835
- h = '0' + h;
11836
- }
11837
- codecstring += h;
11838
- }
11839
- track.codec = codecstring;
11840
- }
11841
- break;
11842
- }
11843
- // PPS
11844
- case 8:
11845
- push = true;
11846
- track.pps = [unit.data];
11847
- break;
11848
- // AUD
11849
- case 9:
11850
- push = true;
11851
- track.audFound = true;
11852
- if (VideoSample) {
11853
- this.pushAccessUnit(VideoSample, track);
11854
- }
11855
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
11856
- break;
11857
- // Filler Data
11858
- case 12:
11859
- push = true;
11860
- break;
11861
- default:
11862
- push = false;
11863
- if (VideoSample) {
11864
- VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
11865
- }
11866
- break;
11867
- }
11868
- if (VideoSample && push) {
11869
- const units = VideoSample.units;
11870
- units.push(unit);
11871
- }
11872
- });
11873
- // if last PES packet, push samples
11874
- if (last && VideoSample) {
11875
- this.pushAccessUnit(VideoSample, track);
11876
- this.VideoSample = null;
11877
- }
11878
- }
11879
- getNALuType(data, offset) {
11880
- return data[offset] & 0x1f;
11881
- }
11882
- readSliceType(data) {
11883
- const eg = new ExpGolomb(data);
11884
- // skip NALu type
11885
- eg.readUByte();
11886
- // discard first_mb_in_slice
11887
- eg.readUEG();
11888
- // return slice_type
11889
- return eg.readUEG();
11890
- }
11891
11472
 
11892
11473
  /**
11893
- * The scaling list is optionally transmitted as part of a sequence parameter
11474
+ * Advance the ExpGolomb decoder past a scaling list. The scaling
11475
+ * list is optionally transmitted as part of a sequence parameter
11894
11476
  * set and is not relevant to transmuxing.
11895
11477
  * @param count the number of entries in this scaling list
11896
11478
  * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
11897
11479
  */
11898
- skipScalingList(count, reader) {
11480
+ skipScalingList(count) {
11899
11481
  let lastScale = 8;
11900
11482
  let nextScale = 8;
11901
11483
  let deltaScale;
11902
11484
  for (let j = 0; j < count; j++) {
11903
11485
  if (nextScale !== 0) {
11904
- deltaScale = reader.readEG();
11486
+ deltaScale = this.readEG();
11905
11487
  nextScale = (lastScale + deltaScale + 256) % 256;
11906
11488
  }
11907
11489
  lastScale = nextScale === 0 ? lastScale : nextScale;
@@ -11916,8 +11498,7 @@ class AvcVideoParser extends BaseVideoParser {
11916
11498
  * sequence parameter set, including the dimensions of the
11917
11499
  * associated video frames.
11918
11500
  */
11919
- readSPS(sps) {
11920
- const eg = new ExpGolomb(sps);
11501
+ readSPS() {
11921
11502
  let frameCropLeftOffset = 0;
11922
11503
  let frameCropRightOffset = 0;
11923
11504
  let frameCropTopOffset = 0;
@@ -11925,13 +11506,13 @@ class AvcVideoParser extends BaseVideoParser {
11925
11506
  let numRefFramesInPicOrderCntCycle;
11926
11507
  let scalingListCount;
11927
11508
  let i;
11928
- const readUByte = eg.readUByte.bind(eg);
11929
- const readBits = eg.readBits.bind(eg);
11930
- const readUEG = eg.readUEG.bind(eg);
11931
- const readBoolean = eg.readBoolean.bind(eg);
11932
- const skipBits = eg.skipBits.bind(eg);
11933
- const skipEG = eg.skipEG.bind(eg);
11934
- const skipUEG = eg.skipUEG.bind(eg);
11509
+ const readUByte = this.readUByte.bind(this);
11510
+ const readBits = this.readBits.bind(this);
11511
+ const readUEG = this.readUEG.bind(this);
11512
+ const readBoolean = this.readBoolean.bind(this);
11513
+ const skipBits = this.skipBits.bind(this);
11514
+ const skipEG = this.skipEG.bind(this);
11515
+ const skipUEG = this.skipUEG.bind(this);
11935
11516
  const skipScalingList = this.skipScalingList.bind(this);
11936
11517
  readUByte();
11937
11518
  const profileIdc = readUByte(); // profile_idc
@@ -11956,9 +11537,9 @@ class AvcVideoParser extends BaseVideoParser {
11956
11537
  if (readBoolean()) {
11957
11538
  // seq_scaling_list_present_flag[ i ]
11958
11539
  if (i < 6) {
11959
- skipScalingList(16, eg);
11540
+ skipScalingList(16);
11960
11541
  } else {
11961
- skipScalingList(64, eg);
11542
+ skipScalingList(64);
11962
11543
  }
11963
11544
  }
11964
11545
  }
@@ -12063,15 +11644,19 @@ class AvcVideoParser extends BaseVideoParser {
12063
11644
  pixelRatio: pixelRatio
12064
11645
  };
12065
11646
  }
11647
+ readSliceType() {
11648
+ // skip NALu type
11649
+ this.readUByte();
11650
+ // discard first_mb_in_slice
11651
+ this.readUEG();
11652
+ // return slice_type
11653
+ return this.readUEG();
11654
+ }
12066
11655
  }
12067
11656
 
12068
- class HevcVideoParser extends BaseVideoParser {
12069
- constructor(...args) {
12070
- super(...args);
12071
- this.initVPS = null;
12072
- }
12073
- parsePES(track, textTrack, pes, last, duration) {
12074
- const units = this.parseNALu(track, pes.data);
11657
+ class AvcVideoParser extends BaseVideoParser {
11658
+ parseAVCPES(track, textTrack, pes, last, duration) {
11659
+ const units = this.parseAVCNALu(track, pes.data);
12075
11660
  let VideoSample = this.VideoSample;
12076
11661
  let push;
12077
11662
  let spsfound = false;
@@ -12087,49 +11672,42 @@ class HevcVideoParser extends BaseVideoParser {
12087
11672
  units.forEach(unit => {
12088
11673
  var _VideoSample2;
12089
11674
  switch (unit.type) {
12090
- // NON-IDR, NON RANDOM ACCESS SLICE
12091
- case 0:
11675
+ // NDR
12092
11676
  case 1:
12093
- case 2:
12094
- case 3:
12095
- case 4:
12096
- case 5:
12097
- case 6:
12098
- case 7:
12099
- case 8:
12100
- case 9:
12101
- if (!VideoSample) {
12102
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12103
- }
12104
- VideoSample.frame = true;
12105
- push = true;
12106
- break;
12107
-
12108
- // CRA, BLA (random access picture)
12109
- case 16:
12110
- case 17:
12111
- case 18:
12112
- case 21:
12113
- push = true;
12114
- if (spsfound) {
12115
- var _VideoSample;
12116
- // handle PES not starting with AUD
12117
- // if we have frame data already, that cannot belong to the same frame, so force a push
12118
- if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
12119
- this.pushAccessUnit(VideoSample, track);
12120
- VideoSample = this.VideoSample = null;
11677
+ {
11678
+ let iskey = false;
11679
+ push = true;
11680
+ const data = unit.data;
11681
+ // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
11682
+ if (spsfound && data.length > 4) {
11683
+ // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
11684
+ const sliceType = new ExpGolomb(data).readSliceType();
11685
+ // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
11686
+ // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
11687
+ // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
11688
+ // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
11689
+ // if (sliceType === 2 || sliceType === 7) {
11690
+ if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
11691
+ iskey = true;
11692
+ }
12121
11693
  }
11694
+ if (iskey) {
11695
+ var _VideoSample;
11696
+ // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
11697
+ if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
11698
+ this.pushAccessUnit(VideoSample, track);
11699
+ VideoSample = this.VideoSample = null;
11700
+ }
11701
+ }
11702
+ if (!VideoSample) {
11703
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11704
+ }
11705
+ VideoSample.frame = true;
11706
+ VideoSample.key = iskey;
11707
+ break;
11708
+ // IDR
12122
11709
  }
12123
- if (!VideoSample) {
12124
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12125
- }
12126
- VideoSample.key = true;
12127
- VideoSample.frame = true;
12128
- break;
12129
-
12130
- // IDR
12131
- case 19:
12132
- case 20:
11710
+ case 5:
12133
11711
  push = true;
12134
11712
  // handle PES not starting with AUD
12135
11713
  // if we have frame data already, that cannot belong to the same frame, so force a push
@@ -12143,76 +11721,48 @@ class HevcVideoParser extends BaseVideoParser {
12143
11721
  VideoSample.key = true;
12144
11722
  VideoSample.frame = true;
12145
11723
  break;
12146
-
12147
11724
  // SEI
12148
- case 39:
12149
- push = true;
12150
- parseSEIMessageFromNALu(unit.data, 2,
12151
- // NALu header size
12152
- pes.pts, textTrack.samples);
12153
- break;
12154
-
12155
- // VPS
12156
- case 32:
12157
- push = true;
12158
- if (!track.vps) {
12159
- const config = this.readVPS(unit.data);
12160
- track.params = _objectSpread2({}, config);
12161
- this.initVPS = unit.data;
11725
+ case 6:
11726
+ {
11727
+ push = true;
11728
+ parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
11729
+ break;
11730
+ // SPS
12162
11731
  }
12163
- track.vps = [unit.data];
12164
- break;
12165
-
12166
- // SPS
12167
- case 33:
12168
- push = true;
12169
- spsfound = true;
12170
- if (typeof track.params === 'object') {
12171
- if (track.vps !== undefined && track.vps[0] !== this.initVPS && track.sps !== undefined && !this.matchSPS(track.sps[0], unit.data)) {
12172
- this.initVPS = track.vps[0];
12173
- track.sps = track.pps = undefined;
12174
- }
12175
- if (!track.sps) {
12176
- const config = this.readSPS(unit.data);
11732
+ case 7:
11733
+ {
11734
+ var _track$pixelRatio, _track$pixelRatio2;
11735
+ push = true;
11736
+ spsfound = true;
11737
+ const sps = unit.data;
11738
+ const expGolombDecoder = new ExpGolomb(sps);
11739
+ const config = expGolombDecoder.readSPS();
11740
+ if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) {
12177
11741
  track.width = config.width;
12178
11742
  track.height = config.height;
12179
11743
  track.pixelRatio = config.pixelRatio;
11744
+ track.sps = [sps];
12180
11745
  track.duration = duration;
12181
- track.codec = config.codecString;
12182
- track.sps = [];
12183
- for (const prop in config.params) {
12184
- track.params[prop] = config.params[prop];
11746
+ const codecarray = sps.subarray(1, 4);
11747
+ let codecstring = 'avc1.';
11748
+ for (let i = 0; i < 3; i++) {
11749
+ let h = codecarray[i].toString(16);
11750
+ if (h.length < 2) {
11751
+ h = '0' + h;
11752
+ }
11753
+ codecstring += h;
12185
11754
  }
11755
+ track.codec = codecstring;
12186
11756
  }
12187
- if (track.vps !== undefined && track.vps[0] === this.initVPS) {
12188
- track.sps.push(unit.data);
12189
- }
12190
- }
12191
- if (!VideoSample) {
12192
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11757
+ break;
12193
11758
  }
12194
- VideoSample.key = true;
12195
- break;
12196
-
12197
11759
  // PPS
12198
- case 34:
11760
+ case 8:
12199
11761
  push = true;
12200
- if (typeof track.params === 'object') {
12201
- if (!track.pps) {
12202
- track.pps = [];
12203
- const config = this.readPPS(unit.data);
12204
- for (const prop in config) {
12205
- track.params[prop] = config[prop];
12206
- }
12207
- }
12208
- if (this.initVPS !== null || track.pps.length === 0) {
12209
- track.pps.push(unit.data);
12210
- }
12211
- }
11762
+ track.pps = [unit.data];
12212
11763
  break;
12213
-
12214
- // ACCESS UNIT DELIMITER
12215
- case 35:
11764
+ // AUD
11765
+ case 9:
12216
11766
  push = true;
12217
11767
  track.audFound = true;
12218
11768
  if (VideoSample) {
@@ -12220,10 +11770,14 @@ class HevcVideoParser extends BaseVideoParser {
12220
11770
  }
12221
11771
  VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12222
11772
  break;
11773
+ // Filler Data
11774
+ case 12:
11775
+ push = true;
11776
+ break;
12223
11777
  default:
12224
11778
  push = false;
12225
11779
  if (VideoSample) {
12226
- VideoSample.debug += 'unknown or irrelevant NAL ' + unit.type + ' ';
11780
+ VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
12227
11781
  }
12228
11782
  break;
12229
11783
  }
@@ -12238,423 +11792,109 @@ class HevcVideoParser extends BaseVideoParser {
12238
11792
  this.VideoSample = null;
12239
11793
  }
12240
11794
  }
12241
- getNALuType(data, offset) {
12242
- return (data[offset] & 0x7e) >>> 1;
12243
- }
12244
- ebsp2rbsp(arr) {
12245
- const dst = new Uint8Array(arr.byteLength);
12246
- let dstIdx = 0;
12247
- for (let i = 0; i < arr.byteLength; i++) {
12248
- if (i >= 2) {
12249
- // Unescape: Skip 0x03 after 00 00
12250
- if (arr[i] === 0x03 && arr[i - 1] === 0x00 && arr[i - 2] === 0x00) {
12251
- continue;
12252
- }
12253
- }
12254
- dst[dstIdx] = arr[i];
12255
- dstIdx++;
12256
- }
12257
- return new Uint8Array(dst.buffer, 0, dstIdx);
12258
- }
12259
- readVPS(vps) {
12260
- const eg = new ExpGolomb(vps);
12261
- // remove header
12262
- eg.readUByte();
12263
- eg.readUByte();
12264
- eg.readBits(4); // video_parameter_set_id
12265
- eg.skipBits(2);
12266
- eg.readBits(6); // max_layers_minus1
12267
- const max_sub_layers_minus1 = eg.readBits(3);
12268
- const temporal_id_nesting_flag = eg.readBoolean();
12269
- // ...vui fps can be here, but empty fps value is not critical for metadata
11795
+ parseAVCNALu(track, array) {
11796
+ const len = array.byteLength;
11797
+ let state = track.naluState || 0;
11798
+ const lastState = state;
11799
+ const units = [];
11800
+ let i = 0;
11801
+ let value;
11802
+ let overflow;
11803
+ let unitType;
11804
+ let lastUnitStart = -1;
11805
+ let lastUnitType = 0;
11806
+ // logger.log('PES:' + Hex.hexDump(array));
12270
11807
 
12271
- return {
12272
- numTemporalLayers: max_sub_layers_minus1 + 1,
12273
- temporalIdNested: temporal_id_nesting_flag
12274
- };
12275
- }
12276
- readSPS(sps) {
12277
- const eg = new ExpGolomb(this.ebsp2rbsp(sps));
12278
- eg.readUByte();
12279
- eg.readUByte();
12280
- eg.readBits(4); //video_parameter_set_id
12281
- const max_sub_layers_minus1 = eg.readBits(3);
12282
- eg.readBoolean(); // temporal_id_nesting_flag
12283
-
12284
- // profile_tier_level
12285
- const general_profile_space = eg.readBits(2);
12286
- const general_tier_flag = eg.readBoolean();
12287
- const general_profile_idc = eg.readBits(5);
12288
- const general_profile_compatibility_flags_1 = eg.readUByte();
12289
- const general_profile_compatibility_flags_2 = eg.readUByte();
12290
- const general_profile_compatibility_flags_3 = eg.readUByte();
12291
- const general_profile_compatibility_flags_4 = eg.readUByte();
12292
- const general_constraint_indicator_flags_1 = eg.readUByte();
12293
- const general_constraint_indicator_flags_2 = eg.readUByte();
12294
- const general_constraint_indicator_flags_3 = eg.readUByte();
12295
- const general_constraint_indicator_flags_4 = eg.readUByte();
12296
- const general_constraint_indicator_flags_5 = eg.readUByte();
12297
- const general_constraint_indicator_flags_6 = eg.readUByte();
12298
- const general_level_idc = eg.readUByte();
12299
- const sub_layer_profile_present_flags = [];
12300
- const sub_layer_level_present_flags = [];
12301
- for (let i = 0; i < max_sub_layers_minus1; i++) {
12302
- sub_layer_profile_present_flags.push(eg.readBoolean());
12303
- sub_layer_level_present_flags.push(eg.readBoolean());
12304
- }
12305
- if (max_sub_layers_minus1 > 0) {
12306
- for (let i = max_sub_layers_minus1; i < 8; i++) {
12307
- eg.readBits(2);
12308
- }
12309
- }
12310
- for (let i = 0; i < max_sub_layers_minus1; i++) {
12311
- if (sub_layer_profile_present_flags[i]) {
12312
- eg.readUByte(); // sub_layer_profile_space, sub_layer_tier_flag, sub_layer_profile_idc
12313
- eg.readUByte();
12314
- eg.readUByte();
12315
- eg.readUByte();
12316
- eg.readUByte(); // sub_layer_profile_compatibility_flag
12317
- eg.readUByte();
12318
- eg.readUByte();
12319
- eg.readUByte();
12320
- eg.readUByte();
12321
- eg.readUByte();
12322
- eg.readUByte();
12323
- }
12324
- if (sub_layer_level_present_flags[i]) {
12325
- eg.readUByte();
12326
- }
12327
- }
12328
- eg.readUEG(); // seq_parameter_set_id
12329
- const chroma_format_idc = eg.readUEG();
12330
- if (chroma_format_idc == 3) {
12331
- eg.skipBits(1); //separate_colour_plane_flag
12332
- }
12333
- const pic_width_in_luma_samples = eg.readUEG();
12334
- const pic_height_in_luma_samples = eg.readUEG();
12335
- const conformance_window_flag = eg.readBoolean();
12336
- let pic_left_offset = 0,
12337
- pic_right_offset = 0,
12338
- pic_top_offset = 0,
12339
- pic_bottom_offset = 0;
12340
- if (conformance_window_flag) {
12341
- pic_left_offset += eg.readUEG();
12342
- pic_right_offset += eg.readUEG();
12343
- pic_top_offset += eg.readUEG();
12344
- pic_bottom_offset += eg.readUEG();
12345
- }
12346
- const bit_depth_luma_minus8 = eg.readUEG();
12347
- const bit_depth_chroma_minus8 = eg.readUEG();
12348
- const log2_max_pic_order_cnt_lsb_minus4 = eg.readUEG();
12349
- const sub_layer_ordering_info_present_flag = eg.readBoolean();
12350
- for (let i = sub_layer_ordering_info_present_flag ? 0 : max_sub_layers_minus1; i <= max_sub_layers_minus1; i++) {
12351
- eg.skipUEG(); // max_dec_pic_buffering_minus1[i]
12352
- eg.skipUEG(); // max_num_reorder_pics[i]
12353
- eg.skipUEG(); // max_latency_increase_plus1[i]
12354
- }
12355
- eg.skipUEG(); // log2_min_luma_coding_block_size_minus3
12356
- eg.skipUEG(); // log2_diff_max_min_luma_coding_block_size
12357
- eg.skipUEG(); // log2_min_transform_block_size_minus2
12358
- eg.skipUEG(); // log2_diff_max_min_transform_block_size
12359
- eg.skipUEG(); // max_transform_hierarchy_depth_inter
12360
- eg.skipUEG(); // max_transform_hierarchy_depth_intra
12361
- const scaling_list_enabled_flag = eg.readBoolean();
12362
- if (scaling_list_enabled_flag) {
12363
- const sps_scaling_list_data_present_flag = eg.readBoolean();
12364
- if (sps_scaling_list_data_present_flag) {
12365
- for (let sizeId = 0; sizeId < 4; sizeId++) {
12366
- for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
12367
- const scaling_list_pred_mode_flag = eg.readBoolean();
12368
- if (!scaling_list_pred_mode_flag) {
12369
- eg.readUEG(); // scaling_list_pred_matrix_id_delta
12370
- } else {
12371
- const coefNum = Math.min(64, 1 << 4 + (sizeId << 1));
12372
- if (sizeId > 1) {
12373
- eg.readEG();
12374
- }
12375
- for (let i = 0; i < coefNum; i++) {
12376
- eg.readEG();
12377
- }
12378
- }
12379
- }
12380
- }
12381
- }
11808
+ if (state === -1) {
11809
+ // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
11810
+ lastUnitStart = 0;
11811
+ // NALu type is value read from offset 0
11812
+ lastUnitType = array[0] & 0x1f;
11813
+ state = 0;
11814
+ i = 1;
12382
11815
  }
12383
- eg.readBoolean(); // amp_enabled_flag
12384
- eg.readBoolean(); // sample_adaptive_offset_enabled_flag
12385
- const pcm_enabled_flag = eg.readBoolean();
12386
- if (pcm_enabled_flag) {
12387
- eg.readUByte();
12388
- eg.skipUEG();
12389
- eg.skipUEG();
12390
- eg.readBoolean();
12391
- }
12392
- const num_short_term_ref_pic_sets = eg.readUEG();
12393
- let num_delta_pocs = 0;
12394
- for (let i = 0; i < num_short_term_ref_pic_sets; i++) {
12395
- let inter_ref_pic_set_prediction_flag = false;
12396
- if (i !== 0) {
12397
- inter_ref_pic_set_prediction_flag = eg.readBoolean();
12398
- }
12399
- if (inter_ref_pic_set_prediction_flag) {
12400
- if (i === num_short_term_ref_pic_sets) {
12401
- eg.readUEG();
12402
- }
12403
- eg.readBoolean();
12404
- eg.readUEG();
12405
- let next_num_delta_pocs = 0;
12406
- for (let j = 0; j <= num_delta_pocs; j++) {
12407
- const used_by_curr_pic_flag = eg.readBoolean();
12408
- let use_delta_flag = false;
12409
- if (!used_by_curr_pic_flag) {
12410
- use_delta_flag = eg.readBoolean();
12411
- }
12412
- if (used_by_curr_pic_flag || use_delta_flag) {
12413
- next_num_delta_pocs++;
12414
- }
12415
- }
12416
- num_delta_pocs = next_num_delta_pocs;
12417
- } else {
12418
- const num_negative_pics = eg.readUEG();
12419
- const num_positive_pics = eg.readUEG();
12420
- num_delta_pocs = num_negative_pics + num_positive_pics;
12421
- for (let j = 0; j < num_negative_pics; j++) {
12422
- eg.readUEG();
12423
- eg.readBoolean();
12424
- }
12425
- for (let j = 0; j < num_positive_pics; j++) {
12426
- eg.readUEG();
12427
- eg.readBoolean();
12428
- }
12429
- }
12430
- }
12431
- const long_term_ref_pics_present_flag = eg.readBoolean();
12432
- if (long_term_ref_pics_present_flag) {
12433
- const num_long_term_ref_pics_sps = eg.readUEG();
12434
- for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
12435
- for (let j = 0; j < log2_max_pic_order_cnt_lsb_minus4 + 4; j++) {
12436
- eg.readBits(1);
12437
- }
12438
- eg.readBits(1);
12439
- }
12440
- }
12441
- let min_spatial_segmentation_idc = 0;
12442
- let sar_width = 1,
12443
- sar_height = 1;
12444
- let fps_fixed = true,
12445
- fps_den = 1,
12446
- fps_num = 0;
12447
- eg.readBoolean(); // sps_temporal_mvp_enabled_flag
12448
- eg.readBoolean(); // strong_intra_smoothing_enabled_flag
12449
- let default_display_window_flag = false;
12450
- const vui_parameters_present_flag = eg.readBoolean();
12451
- if (vui_parameters_present_flag) {
12452
- const aspect_ratio_info_present_flag = eg.readBoolean();
12453
- if (aspect_ratio_info_present_flag) {
12454
- const aspect_ratio_idc = eg.readUByte();
12455
- const sar_width_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];
12456
- const sar_height_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1];
12457
- if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) {
12458
- sar_width = sar_width_table[aspect_ratio_idc - 1];
12459
- sar_height = sar_height_table[aspect_ratio_idc - 1];
12460
- } else if (aspect_ratio_idc === 255) {
12461
- sar_width = eg.readBits(16);
12462
- sar_height = eg.readBits(16);
12463
- }
12464
- }
12465
- const overscan_info_present_flag = eg.readBoolean();
12466
- if (overscan_info_present_flag) {
12467
- eg.readBoolean();
12468
- }
12469
- const video_signal_type_present_flag = eg.readBoolean();
12470
- if (video_signal_type_present_flag) {
12471
- eg.readBits(3);
12472
- eg.readBoolean();
12473
- const colour_description_present_flag = eg.readBoolean();
12474
- if (colour_description_present_flag) {
12475
- eg.readUByte();
12476
- eg.readUByte();
12477
- eg.readUByte();
12478
- }
12479
- }
12480
- const chroma_loc_info_present_flag = eg.readBoolean();
12481
- if (chroma_loc_info_present_flag) {
12482
- eg.readUEG();
12483
- eg.readUEG();
12484
- }
12485
- eg.readBoolean(); // neutral_chroma_indication_flag
12486
- eg.readBoolean(); // field_seq_flag
12487
- eg.readBoolean(); // frame_field_info_present_flag
12488
- default_display_window_flag = eg.readBoolean();
12489
- if (default_display_window_flag) {
12490
- pic_left_offset += eg.readUEG();
12491
- pic_right_offset += eg.readUEG();
12492
- pic_top_offset += eg.readUEG();
12493
- pic_bottom_offset += eg.readUEG();
12494
- }
12495
- const vui_timing_info_present_flag = eg.readBoolean();
12496
- if (vui_timing_info_present_flag) {
12497
- fps_den = eg.readBits(32);
12498
- fps_num = eg.readBits(32);
12499
- const vui_poc_proportional_to_timing_flag = eg.readBoolean();
12500
- if (vui_poc_proportional_to_timing_flag) {
12501
- eg.readUEG();
12502
- }
12503
- const vui_hrd_parameters_present_flag = eg.readBoolean();
12504
- if (vui_hrd_parameters_present_flag) {
12505
- //const commonInfPresentFlag = true;
12506
- //if (commonInfPresentFlag) {
12507
- const nal_hrd_parameters_present_flag = eg.readBoolean();
12508
- const vcl_hrd_parameters_present_flag = eg.readBoolean();
12509
- let sub_pic_hrd_params_present_flag = false;
12510
- if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
12511
- sub_pic_hrd_params_present_flag = eg.readBoolean();
12512
- if (sub_pic_hrd_params_present_flag) {
12513
- eg.readUByte();
12514
- eg.readBits(5);
12515
- eg.readBoolean();
12516
- eg.readBits(5);
12517
- }
12518
- eg.readBits(4); // bit_rate_scale
12519
- eg.readBits(4); // cpb_size_scale
12520
- if (sub_pic_hrd_params_present_flag) {
12521
- eg.readBits(4);
12522
- }
12523
- eg.readBits(5);
12524
- eg.readBits(5);
12525
- eg.readBits(5);
12526
- }
12527
- //}
12528
- for (let i = 0; i <= max_sub_layers_minus1; i++) {
12529
- fps_fixed = eg.readBoolean(); // fixed_pic_rate_general_flag
12530
- const fixed_pic_rate_within_cvs_flag = fps_fixed || eg.readBoolean();
12531
- let low_delay_hrd_flag = false;
12532
- if (fixed_pic_rate_within_cvs_flag) {
12533
- eg.readEG();
12534
- } else {
12535
- low_delay_hrd_flag = eg.readBoolean();
12536
- }
12537
- const cpb_cnt = low_delay_hrd_flag ? 1 : eg.readUEG() + 1;
12538
- if (nal_hrd_parameters_present_flag) {
12539
- for (let j = 0; j < cpb_cnt; j++) {
12540
- eg.readUEG();
12541
- eg.readUEG();
12542
- if (sub_pic_hrd_params_present_flag) {
12543
- eg.readUEG();
12544
- eg.readUEG();
12545
- }
12546
- eg.skipBits(1);
11816
+ while (i < len) {
11817
+ value = array[i++];
11818
+ // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
11819
+ if (!state) {
11820
+ state = value ? 0 : 1;
11821
+ continue;
11822
+ }
11823
+ if (state === 1) {
11824
+ state = value ? 0 : 2;
11825
+ continue;
11826
+ }
11827
+ // here we have state either equal to 2 or 3
11828
+ if (!value) {
11829
+ state = 3;
11830
+ } else if (value === 1) {
11831
+ overflow = i - state - 1;
11832
+ if (lastUnitStart >= 0) {
11833
+ const unit = {
11834
+ data: array.subarray(lastUnitStart, overflow),
11835
+ type: lastUnitType
11836
+ };
11837
+ // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
11838
+ units.push(unit);
11839
+ } else {
11840
+ // lastUnitStart is undefined => this is the first start code found in this PES packet
11841
+ // first check if start code delimiter is overlapping between 2 PES packets,
11842
+ // ie it started in last packet (lastState not zero)
11843
+ // and ended at the beginning of this PES packet (i <= 4 - lastState)
11844
+ const lastUnit = this.getLastNalUnit(track.samples);
11845
+ if (lastUnit) {
11846
+ if (lastState && i <= 4 - lastState) {
11847
+ // start delimiter overlapping between PES packets
11848
+ // strip start delimiter bytes from the end of last NAL unit
11849
+ // check if lastUnit had a state different from zero
11850
+ if (lastUnit.state) {
11851
+ // strip last bytes
11852
+ lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
12547
11853
  }
12548
11854
  }
12549
- if (vcl_hrd_parameters_present_flag) {
12550
- for (let j = 0; j < cpb_cnt; j++) {
12551
- eg.readUEG();
12552
- eg.readUEG();
12553
- if (sub_pic_hrd_params_present_flag) {
12554
- eg.readUEG();
12555
- eg.readUEG();
12556
- }
12557
- eg.skipBits(1);
12558
- }
11855
+ // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
11856
+
11857
+ if (overflow > 0) {
11858
+ // logger.log('first NALU found with overflow:' + overflow);
11859
+ lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
11860
+ lastUnit.state = 0;
12559
11861
  }
12560
11862
  }
12561
11863
  }
12562
- }
12563
- const bitstream_restriction_flag = eg.readBoolean();
12564
- if (bitstream_restriction_flag) {
12565
- eg.readBoolean(); // tiles_fixed_structure_flag
12566
- eg.readBoolean(); // motion_vectors_over_pic_boundaries_flag
12567
- eg.readBoolean(); // restricted_ref_pic_lists_flag
12568
- min_spatial_segmentation_idc = eg.readUEG();
12569
- }
12570
- }
12571
- let width = pic_width_in_luma_samples,
12572
- height = pic_height_in_luma_samples;
12573
- if (conformance_window_flag || default_display_window_flag) {
12574
- let chroma_scale_w = 1,
12575
- chroma_scale_h = 1;
12576
- if (chroma_format_idc === 1) {
12577
- // YUV 420
12578
- chroma_scale_w = chroma_scale_h = 2;
12579
- } else if (chroma_format_idc == 2) {
12580
- // YUV 422
12581
- chroma_scale_w = 2;
12582
- }
12583
- width = pic_width_in_luma_samples - chroma_scale_w * pic_right_offset - chroma_scale_w * pic_left_offset;
12584
- height = pic_height_in_luma_samples - chroma_scale_h * pic_bottom_offset - chroma_scale_h * pic_top_offset;
12585
- }
12586
- const profile_space_string = general_profile_space ? ['A', 'B', 'C'][general_profile_space] : '';
12587
- const profile_compatibility_buf = general_profile_compatibility_flags_1 << 24 | general_profile_compatibility_flags_2 << 16 | general_profile_compatibility_flags_3 << 8 | general_profile_compatibility_flags_4;
12588
- let profile_compatibility_rev = 0;
12589
- for (let i = 0; i < 32; i++) {
12590
- profile_compatibility_rev = (profile_compatibility_rev | (profile_compatibility_buf >> i & 1) << 31 - i) >>> 0; // reverse bit position (and cast as UInt32)
12591
- }
12592
- let profile_compatibility_flags_string = profile_compatibility_rev.toString(16);
12593
- if (general_profile_idc === 1 && profile_compatibility_flags_string === '2') {
12594
- profile_compatibility_flags_string = '6';
12595
- }
12596
- const tier_flag_string = general_tier_flag ? 'H' : 'L';
12597
- return {
12598
- codecString: `hvc1.${profile_space_string}${general_profile_idc}.${profile_compatibility_flags_string}.${tier_flag_string}${general_level_idc}.B0`,
12599
- params: {
12600
- general_tier_flag,
12601
- general_profile_idc,
12602
- general_profile_space,
12603
- general_profile_compatibility_flags: [general_profile_compatibility_flags_1, general_profile_compatibility_flags_2, general_profile_compatibility_flags_3, general_profile_compatibility_flags_4],
12604
- general_constraint_indicator_flags: [general_constraint_indicator_flags_1, general_constraint_indicator_flags_2, general_constraint_indicator_flags_3, general_constraint_indicator_flags_4, general_constraint_indicator_flags_5, general_constraint_indicator_flags_6],
12605
- general_level_idc,
12606
- bit_depth: bit_depth_luma_minus8 + 8,
12607
- bit_depth_luma_minus8,
12608
- bit_depth_chroma_minus8,
12609
- min_spatial_segmentation_idc,
12610
- chroma_format_idc: chroma_format_idc,
12611
- frame_rate: {
12612
- fixed: fps_fixed,
12613
- fps: fps_num / fps_den
11864
+ // check if we can read unit type
11865
+ if (i < len) {
11866
+ unitType = array[i] & 0x1f;
11867
+ // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
11868
+ lastUnitStart = i;
11869
+ lastUnitType = unitType;
11870
+ state = 0;
11871
+ } else {
11872
+ // not enough byte to read unit type. let's read it on next PES parsing
11873
+ state = -1;
12614
11874
  }
12615
- },
12616
- width,
12617
- height,
12618
- pixelRatio: [sar_width, sar_height]
12619
- };
12620
- }
12621
- readPPS(pps) {
12622
- const eg = new ExpGolomb(this.ebsp2rbsp(pps));
12623
- eg.readUByte();
12624
- eg.readUByte();
12625
- eg.skipUEG(); // pic_parameter_set_id
12626
- eg.skipUEG(); // seq_parameter_set_id
12627
- eg.skipBits(2); // dependent_slice_segments_enabled_flag, output_flag_present_flag
12628
- eg.skipBits(3); // num_extra_slice_header_bits
12629
- eg.skipBits(2); // sign_data_hiding_enabled_flag, cabac_init_present_flag
12630
- eg.skipUEG();
12631
- eg.skipUEG();
12632
- eg.skipEG(); // init_qp_minus26
12633
- eg.skipBits(2); // constrained_intra_pred_flag, transform_skip_enabled_flag
12634
- const cu_qp_delta_enabled_flag = eg.readBoolean();
12635
- if (cu_qp_delta_enabled_flag) {
12636
- eg.skipUEG();
12637
- }
12638
- eg.skipEG(); // cb_qp_offset
12639
- eg.skipEG(); // cr_qp_offset
12640
- eg.skipBits(4); // pps_slice_chroma_qp_offsets_present_flag, weighted_pred_flag, weighted_bipred_flag, transquant_bypass_enabled_flag
12641
- const tiles_enabled_flag = eg.readBoolean();
12642
- const entropy_coding_sync_enabled_flag = eg.readBoolean();
12643
- let parallelismType = 1; // slice-based parallel decoding
12644
- if (entropy_coding_sync_enabled_flag && tiles_enabled_flag) {
12645
- parallelismType = 0; // mixed-type parallel decoding
12646
- } else if (entropy_coding_sync_enabled_flag) {
12647
- parallelismType = 3; // wavefront-based parallel decoding
12648
- } else if (tiles_enabled_flag) {
12649
- parallelismType = 2; // tile-based parallel decoding
11875
+ } else {
11876
+ state = 0;
11877
+ }
12650
11878
  }
12651
- return {
12652
- parallelismType
12653
- };
12654
- }
12655
- matchSPS(sps1, sps2) {
12656
- // compare without headers and VPS related params
12657
- return String.fromCharCode.apply(null, sps1).substr(3) === String.fromCharCode.apply(null, sps2).substr(3);
11879
+ if (lastUnitStart >= 0 && state >= 0) {
11880
+ const unit = {
11881
+ data: array.subarray(lastUnitStart, len),
11882
+ type: lastUnitType,
11883
+ state: state
11884
+ };
11885
+ units.push(unit);
11886
+ // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
11887
+ }
11888
+ // no NALu found
11889
+ if (units.length === 0) {
11890
+ // append pes.data to previous NAL unit
11891
+ const lastUnit = this.getLastNalUnit(track.samples);
11892
+ if (lastUnit) {
11893
+ lastUnit.data = appendUint8Array(lastUnit.data, array);
11894
+ }
11895
+ }
11896
+ track.naluState = state;
11897
+ return units;
12658
11898
  }
12659
11899
  }
12660
11900
 
@@ -12672,7 +11912,7 @@ class SampleAesDecrypter {
12672
11912
  });
12673
11913
  }
12674
11914
  decryptBuffer(encryptedData) {
12675
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
11915
+ return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
12676
11916
  }
12677
11917
 
12678
11918
  // AAC - encrypt all full 16 bytes blocks starting from offset 16
@@ -12786,7 +12026,7 @@ class TSDemuxer {
12786
12026
  this.observer = observer;
12787
12027
  this.config = config;
12788
12028
  this.typeSupported = typeSupported;
12789
- this.videoParser = null;
12029
+ this.videoParser = new AvcVideoParser();
12790
12030
  }
12791
12031
  static probe(data) {
12792
12032
  const syncOffset = TSDemuxer.syncOffset(data);
@@ -12951,21 +12191,7 @@ class TSDemuxer {
12951
12191
  case videoPid:
12952
12192
  if (stt) {
12953
12193
  if (videoData && (pes = parsePES(videoData))) {
12954
- if (this.videoParser === null) {
12955
- switch (videoTrack.segmentCodec) {
12956
- case 'avc':
12957
- this.videoParser = new AvcVideoParser();
12958
- break;
12959
- case 'hevc':
12960
- {
12961
- this.videoParser = new HevcVideoParser();
12962
- }
12963
- break;
12964
- }
12965
- }
12966
- if (this.videoParser !== null) {
12967
- this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
12968
- }
12194
+ this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
12969
12195
  }
12970
12196
  videoData = {
12971
12197
  data: [],
@@ -13132,22 +12358,8 @@ class TSDemuxer {
13132
12358
  // try to parse last PES packets
13133
12359
  let pes;
13134
12360
  if (videoData && (pes = parsePES(videoData))) {
13135
- if (this.videoParser === null) {
13136
- switch (videoTrack.segmentCodec) {
13137
- case 'avc':
13138
- this.videoParser = new AvcVideoParser();
13139
- break;
13140
- case 'hevc':
13141
- {
13142
- this.videoParser = new HevcVideoParser();
13143
- }
13144
- break;
13145
- }
13146
- }
13147
- if (this.videoParser !== null) {
13148
- this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
13149
- videoTrack.pesData = null;
13150
- }
12361
+ this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
12362
+ videoTrack.pesData = null;
13151
12363
  } else {
13152
12364
  // either avcData null or PES truncated, keep it for next frag parsing
13153
12365
  videoTrack.pesData = videoData;
@@ -13480,14 +12692,7 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
13480
12692
  logger.warn('Unsupported EC-3 in M2TS found');
13481
12693
  break;
13482
12694
  case 0x24:
13483
- // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
13484
- {
13485
- if (result.videoPid === -1) {
13486
- result.videoPid = pid;
13487
- result.segmentVideoCodec = 'hevc';
13488
- logger.log('HEVC in M2TS found');
13489
- }
13490
- }
12695
+ logger.warn('Unsupported HEVC in M2TS found');
13491
12696
  break;
13492
12697
  }
13493
12698
  // move to the next table entry
@@ -13710,8 +12915,6 @@ class MP4 {
13710
12915
  avc1: [],
13711
12916
  // codingname
13712
12917
  avcC: [],
13713
- hvc1: [],
13714
- hvcC: [],
13715
12918
  btrt: [],
13716
12919
  dinf: [],
13717
12920
  dref: [],
@@ -14136,10 +13339,8 @@ class MP4 {
14136
13339
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
14137
13340
  }
14138
13341
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
14139
- } else if (track.segmentCodec === 'avc') {
14140
- return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
14141
13342
  } else {
14142
- return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
13343
+ return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
14143
13344
  }
14144
13345
  }
14145
13346
  static tkhd(track) {
@@ -14238,122 +13439,44 @@ class MP4 {
14238
13439
  const arraylen = 12 + 16 * len;
14239
13440
  const array = new Uint8Array(arraylen);
14240
13441
  let i;
14241
- let sample;
14242
- let duration;
14243
- let size;
14244
- let flags;
14245
- let cts;
14246
- offset += 8 + arraylen;
14247
- array.set([track.type === 'video' ? 0x01 : 0x00,
14248
- // version 1 for video with signed-int sample_composition_time_offset
14249
- 0x00, 0x0f, 0x01,
14250
- // flags
14251
- len >>> 24 & 0xff, len >>> 16 & 0xff, len >>> 8 & 0xff, len & 0xff,
14252
- // sample_count
14253
- offset >>> 24 & 0xff, offset >>> 16 & 0xff, offset >>> 8 & 0xff, offset & 0xff // data_offset
14254
- ], 0);
14255
- for (i = 0; i < len; i++) {
14256
- sample = samples[i];
14257
- duration = sample.duration;
14258
- size = sample.size;
14259
- flags = sample.flags;
14260
- cts = sample.cts;
14261
- array.set([duration >>> 24 & 0xff, duration >>> 16 & 0xff, duration >>> 8 & 0xff, duration & 0xff,
14262
- // sample_duration
14263
- size >>> 24 & 0xff, size >>> 16 & 0xff, size >>> 8 & 0xff, size & 0xff,
14264
- // sample_size
14265
- flags.isLeading << 2 | flags.dependsOn, flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.paddingValue << 1 | flags.isNonSync, flags.degradPrio & 0xf0 << 8, flags.degradPrio & 0x0f,
14266
- // sample_flags
14267
- cts >>> 24 & 0xff, cts >>> 16 & 0xff, cts >>> 8 & 0xff, cts & 0xff // sample_composition_time_offset
14268
- ], 12 + 16 * i);
14269
- }
14270
- return MP4.box(MP4.types.trun, array);
14271
- }
14272
- static initSegment(tracks) {
14273
- if (!MP4.types) {
14274
- MP4.init();
14275
- }
14276
- const movie = MP4.moov(tracks);
14277
- const result = appendUint8Array(MP4.FTYP, movie);
14278
- return result;
14279
- }
14280
- static hvc1(track) {
14281
- const ps = track.params;
14282
- const units = [track.vps, track.sps, track.pps];
14283
- const NALuLengthSize = 4;
14284
- const config = new Uint8Array([0x01, ps.general_profile_space << 6 | (ps.general_tier_flag ? 32 : 0) | ps.general_profile_idc, ps.general_profile_compatibility_flags[0], ps.general_profile_compatibility_flags[1], ps.general_profile_compatibility_flags[2], ps.general_profile_compatibility_flags[3], ps.general_constraint_indicator_flags[0], ps.general_constraint_indicator_flags[1], ps.general_constraint_indicator_flags[2], ps.general_constraint_indicator_flags[3], ps.general_constraint_indicator_flags[4], ps.general_constraint_indicator_flags[5], ps.general_level_idc, 240 | ps.min_spatial_segmentation_idc >> 8, 255 & ps.min_spatial_segmentation_idc, 252 | ps.parallelismType, 252 | ps.chroma_format_idc, 248 | ps.bit_depth_luma_minus8, 248 | ps.bit_depth_chroma_minus8, 0x00, parseInt(ps.frame_rate.fps), NALuLengthSize - 1 | ps.temporal_id_nested << 2 | ps.num_temporal_layers << 3 | (ps.frame_rate.fixed ? 64 : 0), units.length]);
14285
-
14286
- // compute hvcC size in bytes
14287
- let length = config.length;
14288
- for (let i = 0; i < units.length; i += 1) {
14289
- length += 3;
14290
- for (let j = 0; j < units[i].length; j += 1) {
14291
- length += 2 + units[i][j].length;
14292
- }
14293
- }
14294
- const hvcC = new Uint8Array(length);
14295
- hvcC.set(config, 0);
14296
- length = config.length;
14297
- // append parameter set units: one vps, one or more sps and pps
14298
- const iMax = units.length - 1;
14299
- for (let i = 0; i < units.length; i += 1) {
14300
- hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
14301
- length += 3;
14302
- for (let j = 0; j < units[i].length; j += 1) {
14303
- hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
14304
- length += 2;
14305
- hvcC.set(units[i][j], length);
14306
- length += units[i][j].length;
14307
- }
14308
- }
14309
- const hvcc = MP4.box(MP4.types.hvcC, hvcC);
14310
- const width = track.width;
14311
- const height = track.height;
14312
- const hSpacing = track.pixelRatio[0];
14313
- const vSpacing = track.pixelRatio[1];
14314
- return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
14315
- // reserved
14316
- 0x00, 0x00, 0x00,
14317
- // reserved
14318
- 0x00, 0x01,
14319
- // data_reference_index
14320
- 0x00, 0x00,
14321
- // pre_defined
14322
- 0x00, 0x00,
14323
- // reserved
14324
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14325
- // pre_defined
14326
- width >> 8 & 0xff, width & 0xff,
14327
- // width
14328
- height >> 8 & 0xff, height & 0xff,
14329
- // height
14330
- 0x00, 0x48, 0x00, 0x00,
14331
- // horizresolution
14332
- 0x00, 0x48, 0x00, 0x00,
14333
- // vertresolution
14334
- 0x00, 0x00, 0x00, 0x00,
14335
- // reserved
14336
- 0x00, 0x01,
14337
- // frame_count
14338
- 0x12, 0x64, 0x61, 0x69, 0x6c,
14339
- // dailymotion/hls.js
14340
- 0x79, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x68, 0x6c, 0x73, 0x2e, 0x6a, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14341
- // compressorname
14342
- 0x00, 0x18,
14343
- // depth = 24
14344
- 0x11, 0x11]),
14345
- // pre_defined = -1
14346
- hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
14347
- // bufferSizeDB
14348
- 0x00, 0x2d, 0xc6, 0xc0,
14349
- // maxBitrate
14350
- 0x00, 0x2d, 0xc6, 0xc0])),
14351
- // avgBitrate
14352
- MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
14353
- // hSpacing
14354
- hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
14355
- // vSpacing
14356
- vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
13442
+ let sample;
13443
+ let duration;
13444
+ let size;
13445
+ let flags;
13446
+ let cts;
13447
+ offset += 8 + arraylen;
13448
+ array.set([track.type === 'video' ? 0x01 : 0x00,
13449
+ // version 1 for video with signed-int sample_composition_time_offset
13450
+ 0x00, 0x0f, 0x01,
13451
+ // flags
13452
+ len >>> 24 & 0xff, len >>> 16 & 0xff, len >>> 8 & 0xff, len & 0xff,
13453
+ // sample_count
13454
+ offset >>> 24 & 0xff, offset >>> 16 & 0xff, offset >>> 8 & 0xff, offset & 0xff // data_offset
13455
+ ], 0);
13456
+ for (i = 0; i < len; i++) {
13457
+ sample = samples[i];
13458
+ duration = sample.duration;
13459
+ size = sample.size;
13460
+ flags = sample.flags;
13461
+ cts = sample.cts;
13462
+ array.set([duration >>> 24 & 0xff, duration >>> 16 & 0xff, duration >>> 8 & 0xff, duration & 0xff,
13463
+ // sample_duration
13464
+ size >>> 24 & 0xff, size >>> 16 & 0xff, size >>> 8 & 0xff, size & 0xff,
13465
+ // sample_size
13466
+ flags.isLeading << 2 | flags.dependsOn, flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.paddingValue << 1 | flags.isNonSync, flags.degradPrio & 0xf0 << 8, flags.degradPrio & 0x0f,
13467
+ // sample_flags
13468
+ cts >>> 24 & 0xff, cts >>> 16 & 0xff, cts >>> 8 & 0xff, cts & 0xff // sample_composition_time_offset
13469
+ ], 12 + 16 * i);
13470
+ }
13471
+ return MP4.box(MP4.types.trun, array);
13472
+ }
13473
+ static initSegment(tracks) {
13474
+ if (!MP4.types) {
13475
+ MP4.init();
13476
+ }
13477
+ const movie = MP4.moov(tracks);
13478
+ const result = appendUint8Array(MP4.FTYP, movie);
13479
+ return result;
14357
13480
  }
14358
13481
  }
14359
13482
  MP4.types = void 0;
@@ -14736,9 +13859,9 @@ class MP4Remuxer {
14736
13859
  const foundOverlap = delta < -1;
14737
13860
  if (foundHole || foundOverlap) {
14738
13861
  if (foundHole) {
14739
- logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
13862
+ logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
14740
13863
  } else {
14741
- logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
13864
+ logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
14742
13865
  }
14743
13866
  if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
14744
13867
  firstDTS = nextAvcDts;
@@ -14747,24 +13870,12 @@ class MP4Remuxer {
14747
13870
  inputSamples[0].dts = firstDTS;
14748
13871
  inputSamples[0].pts = firstPTS;
14749
13872
  } else {
14750
- let isPTSOrderRetained = true;
14751
13873
  for (let i = 0; i < inputSamples.length; i++) {
14752
- if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
13874
+ if (inputSamples[i].dts > firstPTS) {
14753
13875
  break;
14754
13876
  }
14755
- const prevPTS = inputSamples[i].pts;
14756
13877
  inputSamples[i].dts -= delta;
14757
13878
  inputSamples[i].pts -= delta;
14758
-
14759
- // check to see if this sample's PTS order has changed
14760
- // relative to the next one
14761
- if (i < inputSamples.length - 1) {
14762
- const nextSamplePTS = inputSamples[i + 1].pts;
14763
- const currentSamplePTS = inputSamples[i].pts;
14764
- const currentOrder = nextSamplePTS <= currentSamplePTS;
14765
- const prevOrder = nextSamplePTS <= prevPTS;
14766
- isPTSOrderRetained = currentOrder == prevOrder;
14767
- }
14768
13879
  }
14769
13880
  }
14770
13881
  logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
@@ -14912,7 +14023,7 @@ class MP4Remuxer {
14912
14023
  }
14913
14024
  }
14914
14025
  }
14915
- // next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14026
+ // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14916
14027
  mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
14917
14028
  this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
14918
14029
  this.videoSampleDuration = mp4SampleDuration;
@@ -15045,7 +14156,7 @@ class MP4Remuxer {
15045
14156
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
15046
14157
  for (let j = 0; j < missing; j++) {
15047
14158
  const newStamp = Math.max(nextPts, 0);
15048
- let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14159
+ let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
15049
14160
  if (!fillFrame) {
15050
14161
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
15051
14162
  fillFrame = sample.unit.subarray();
@@ -15173,7 +14284,7 @@ class MP4Remuxer {
15173
14284
  // samples count of this segment's duration
15174
14285
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
15175
14286
  // silent frame
15176
- const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14287
+ const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
15177
14288
  logger.warn('[mp4-remuxer]: remux empty Audio');
15178
14289
  // Can't remux if we can't generate a silent frame...
15179
14290
  if (!silentFrame) {
@@ -15567,15 +14678,13 @@ class Transmuxer {
15567
14678
  initSegmentData
15568
14679
  } = transmuxConfig;
15569
14680
  const keyData = getEncryptionType(uintData, decryptdata);
15570
- if (keyData && isFullSegmentEncryption(keyData.method)) {
14681
+ if (keyData && keyData.method === 'AES-128') {
15571
14682
  const decrypter = this.getDecrypter();
15572
- const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
15573
-
15574
14683
  // Software decryption is synchronous; webCrypto is not
15575
14684
  if (decrypter.isSync()) {
15576
14685
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
15577
14686
  // data is handled in the flush() call
15578
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
14687
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
15579
14688
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
15580
14689
  const loadingParts = chunkMeta.part > -1;
15581
14690
  if (loadingParts) {
@@ -15587,7 +14696,7 @@ class Transmuxer {
15587
14696
  }
15588
14697
  uintData = new Uint8Array(decryptedData);
15589
14698
  } else {
15590
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
14699
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
15591
14700
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
15592
14701
  // the decrypted data has been transmuxed
15593
14702
  const result = this.push(decryptedData, null, chunkMeta);
@@ -16241,7 +15350,14 @@ class TransmuxerInterface {
16241
15350
  this.observer = new EventEmitter();
16242
15351
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
16243
15352
  this.observer.on(Events.ERROR, forwardMessage);
16244
- const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
15353
+ const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
15354
+ isTypeSupported: () => false
15355
+ };
15356
+ const m2tsTypeSupported = {
15357
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
15358
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
15359
+ ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
15360
+ };
16245
15361
 
16246
15362
  // navigator.vendor is not always available in Web Worker
16247
15363
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -16529,7 +15645,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
16529
15645
 
16530
15646
  class AudioStreamController extends BaseStreamController {
16531
15647
  constructor(hls, fragmentTracker, keyLoader) {
16532
- super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
15648
+ super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);
16533
15649
  this.videoBuffer = null;
16534
15650
  this.videoTrackCC = -1;
16535
15651
  this.waitingVideoCC = -1;
@@ -16541,24 +15657,27 @@ class AudioStreamController extends BaseStreamController {
16541
15657
  this.flushing = false;
16542
15658
  this.bufferFlushed = false;
16543
15659
  this.cachedTrackLoadedData = null;
16544
- this.registerListeners();
15660
+ this._registerListeners();
16545
15661
  }
16546
15662
  onHandlerDestroying() {
16547
- this.unregisterListeners();
15663
+ this._unregisterListeners();
16548
15664
  super.onHandlerDestroying();
16549
15665
  this.mainDetails = null;
16550
15666
  this.bufferedTrack = null;
16551
15667
  this.switchingTrack = null;
16552
15668
  }
16553
- registerListeners() {
16554
- super.registerListeners();
15669
+ _registerListeners() {
16555
15670
  const {
16556
15671
  hls
16557
15672
  } = this;
15673
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
15674
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
15675
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16558
15676
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16559
15677
  hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
16560
15678
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
16561
15679
  hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15680
+ hls.on(Events.ERROR, this.onError, this);
16562
15681
  hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
16563
15682
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
16564
15683
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -16566,18 +15685,18 @@ class AudioStreamController extends BaseStreamController {
16566
15685
  hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
16567
15686
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
16568
15687
  }
16569
- unregisterListeners() {
15688
+ _unregisterListeners() {
16570
15689
  const {
16571
15690
  hls
16572
15691
  } = this;
16573
- if (!hls) {
16574
- return;
16575
- }
16576
- super.unregisterListeners();
15692
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
15693
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
15694
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16577
15695
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16578
15696
  hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
16579
15697
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
16580
15698
  hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15699
+ hls.off(Events.ERROR, this.onError, this);
16581
15700
  hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
16582
15701
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
16583
15702
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -16720,9 +15839,7 @@ class AudioStreamController extends BaseStreamController {
16720
15839
  this.fragmentTracker.removeFragment(waitingData.frag);
16721
15840
  this.waitingData = null;
16722
15841
  this.waitingVideoCC = -1;
16723
- if (this.state !== State.STOPPED) {
16724
- this.state = State.IDLE;
16725
- }
15842
+ this.state = State.IDLE;
16726
15843
  }
16727
15844
  }
16728
15845
  resetLoadingState() {
@@ -16748,13 +15865,12 @@ class AudioStreamController extends BaseStreamController {
16748
15865
  } = this;
16749
15866
  const config = hls.config;
16750
15867
 
16751
- // 1. if buffering is suspended
16752
- // 2. if video not attached AND
15868
+ // 1. if video not attached AND
16753
15869
  // start fragment already requested OR start frag prefetch not enabled
16754
- // 3. if tracks or track not loaded and selected
15870
+ // 2. if tracks or track not loaded and selected
16755
15871
  // then exit loop
16756
15872
  // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
16757
- if (!this.buffering || !media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
15873
+ if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
16758
15874
  return;
16759
15875
  }
16760
15876
  const levelInfo = levels[trackId];
@@ -16783,8 +15899,9 @@ class AudioStreamController extends BaseStreamController {
16783
15899
  this.state = State.ENDED;
16784
15900
  return;
16785
15901
  }
15902
+ const mainBufferInfo = this.getFwdBufferInfo(this.videoBuffer ? this.videoBuffer : this.media, PlaylistLevelType.MAIN);
16786
15903
  const bufferLen = bufferInfo.len;
16787
- const maxBufLen = hls.maxBufferLength;
15904
+ const maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len);
16788
15905
  const fragments = trackDetails.fragments;
16789
15906
  const start = fragments[0].start;
16790
15907
  let targetBufferTime = this.flushing ? this.getLoadPosition() : bufferInfo.end;
@@ -16819,25 +15936,32 @@ class AudioStreamController extends BaseStreamController {
16819
15936
  this.bufferFlushed = true;
16820
15937
  return;
16821
15938
  }
16822
- if (!trackDetails.live || targetBufferTime < this.hls.liveSyncPosition) {
16823
- // Request audio segments up to one fragment ahead of main buffer
16824
- const mainBufferInfo = this.getFwdBufferInfo(this.videoBuffer ? this.videoBuffer : this.media, PlaylistLevelType.MAIN);
16825
- const atBufferSyncLimit = !!mainBufferInfo && frag.start > mainBufferInfo.end + frag.duration;
16826
- if (atBufferSyncLimit) {
16827
- // Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo
16828
- const mainFrag = this.fragmentTracker.getFragAtPos(frag.start, PlaylistLevelType.MAIN);
16829
- if (mainFrag === null) {
16830
- return;
16831
- }
16832
- // Bridge gaps in main buffer (also prevents loop loading at gaps)
16833
- atGap || (atGap = !!mainFrag.gap || mainBufferInfo.len === 0);
16834
- if (!atGap || bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end) {
16835
- return;
16836
- }
15939
+
15940
+ // Buffer audio up to one target duration ahead of main buffer
15941
+ const atBufferSyncLimit = mainBufferInfo && frag.start > mainBufferInfo.end + trackDetails.targetduration;
15942
+ if (atBufferSyncLimit ||
15943
+ // Or wait for main buffer after buffing some audio
15944
+ !(mainBufferInfo != null && mainBufferInfo.len) && bufferInfo.len) {
15945
+ // Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo
15946
+ const mainFrag = this.getAppendedFrag(frag.start, PlaylistLevelType.MAIN);
15947
+ if (mainFrag === null) {
15948
+ return;
15949
+ }
15950
+ // Bridge gaps in main buffer
15951
+ atGap || (atGap = !!mainFrag.gap || !!atBufferSyncLimit && mainBufferInfo.len === 0);
15952
+ if (atBufferSyncLimit && !atGap || atGap && bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end) {
15953
+ return;
16837
15954
  }
16838
15955
  }
16839
15956
  this.loadFragment(frag, levelInfo, targetBufferTime);
16840
15957
  }
15958
+ getMaxBufferLength(mainBufferLength) {
15959
+ const maxConfigBuffer = super.getMaxBufferLength();
15960
+ if (!mainBufferLength) {
15961
+ return maxConfigBuffer;
15962
+ }
15963
+ return Math.min(Math.max(maxConfigBuffer, mainBufferLength), this.config.maxMaxBufferLength);
15964
+ }
16841
15965
  onMediaDetaching() {
16842
15966
  this.videoBuffer = null;
16843
15967
  this.bufferFlushed = this.flushing = false;
@@ -16862,25 +15986,26 @@ class AudioStreamController extends BaseStreamController {
16862
15986
  this.removeUnbufferedFrags(fragCurrent.start);
16863
15987
  }
16864
15988
  this.resetLoadingState();
15989
+ // destroy useless transmuxer when switching audio to main
15990
+ if (!altAudio) {
15991
+ this.resetTransmuxer();
15992
+ } else {
15993
+ // switching to audio track, start timer if not already started
15994
+ this.setInterval(TICK_INTERVAL$2);
15995
+ }
16865
15996
 
16866
15997
  // should we switch tracks ?
16867
15998
  if (altAudio) {
16868
15999
  this.switchingTrack = data;
16869
16000
  // main audio track are handled by stream-controller, just do something if switching to alt audio track
16001
+ this.state = State.IDLE;
16870
16002
  this.flushAudioIfNeeded(data);
16871
- if (this.state !== State.STOPPED) {
16872
- // switching to audio track, start timer if not already started
16873
- this.setInterval(TICK_INTERVAL$2);
16874
- this.state = State.IDLE;
16875
- this.tick();
16876
- }
16877
16003
  } else {
16878
- // destroy useless transmuxer when switching audio to main
16879
- this.resetTransmuxer();
16880
16004
  this.switchingTrack = null;
16881
16005
  this.bufferedTrack = data;
16882
- this.clearInterval();
16006
+ this.state = State.STOPPED;
16883
16007
  }
16008
+ this.tick();
16884
16009
  }
16885
16010
  onManifestLoading() {
16886
16011
  this.fragmentTracker.removeAllFragments();
@@ -17303,7 +16428,7 @@ class AudioStreamController extends BaseStreamController {
17303
16428
 
17304
16429
  class AudioTrackController extends BasePlaylistController {
17305
16430
  constructor(hls) {
17306
- super(hls, 'audio-track-controller');
16431
+ super(hls, '[audio-track-controller]');
17307
16432
  this.tracks = [];
17308
16433
  this.groupIds = null;
17309
16434
  this.tracksInGroup = [];
@@ -17622,23 +16747,26 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
17622
16747
 
17623
16748
  class SubtitleStreamController extends BaseStreamController {
17624
16749
  constructor(hls, fragmentTracker, keyLoader) {
17625
- super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
16750
+ super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);
17626
16751
  this.currentTrackId = -1;
17627
16752
  this.tracksBuffered = [];
17628
16753
  this.mainDetails = null;
17629
- this.registerListeners();
16754
+ this._registerListeners();
17630
16755
  }
17631
16756
  onHandlerDestroying() {
17632
- this.unregisterListeners();
16757
+ this._unregisterListeners();
17633
16758
  super.onHandlerDestroying();
17634
16759
  this.mainDetails = null;
17635
16760
  }
17636
- registerListeners() {
17637
- super.registerListeners();
16761
+ _registerListeners() {
17638
16762
  const {
17639
16763
  hls
17640
16764
  } = this;
16765
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
16766
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
16767
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
17641
16768
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16769
+ hls.on(Events.ERROR, this.onError, this);
17642
16770
  hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
17643
16771
  hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
17644
16772
  hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -17646,12 +16774,15 @@ class SubtitleStreamController extends BaseStreamController {
17646
16774
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
17647
16775
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
17648
16776
  }
17649
- unregisterListeners() {
17650
- super.unregisterListeners();
16777
+ _unregisterListeners() {
17651
16778
  const {
17652
16779
  hls
17653
16780
  } = this;
16781
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
16782
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
16783
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
17654
16784
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16785
+ hls.off(Events.ERROR, this.onError, this);
17655
16786
  hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
17656
16787
  hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
17657
16788
  hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -17797,7 +16928,7 @@ class SubtitleStreamController extends BaseStreamController {
17797
16928
  } else {
17798
16929
  this.mediaBuffer = null;
17799
16930
  }
17800
- if (currentTrack && this.state !== State.STOPPED) {
16931
+ if (currentTrack) {
17801
16932
  this.setInterval(TICK_INTERVAL$1);
17802
16933
  }
17803
16934
  }
@@ -17878,10 +17009,10 @@ class SubtitleStreamController extends BaseStreamController {
17878
17009
  return;
17879
17010
  }
17880
17011
  // check to see if the payload needs to be decrypted
17881
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
17012
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
17882
17013
  const startTime = performance.now();
17883
17014
  // decrypt the subtitles
17884
- this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
17015
+ this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
17885
17016
  hls.trigger(Events.ERROR, {
17886
17017
  type: ErrorTypes.MEDIA_ERROR,
17887
17018
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -17930,8 +17061,9 @@ class SubtitleStreamController extends BaseStreamController {
17930
17061
  end: targetBufferTime,
17931
17062
  len: bufferLen
17932
17063
  } = bufferedInfo;
17064
+ const mainBufferInfo = this.getFwdBufferInfo(this.media, PlaylistLevelType.MAIN);
17933
17065
  const trackDetails = track.details;
17934
- const maxBufLen = this.hls.maxBufferLength + trackDetails.levelTargetDuration;
17066
+ const maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len) + trackDetails.levelTargetDuration;
17935
17067
  if (bufferLen > maxBufLen) {
17936
17068
  return;
17937
17069
  }
@@ -17968,6 +17100,13 @@ class SubtitleStreamController extends BaseStreamController {
17968
17100
  }
17969
17101
  }
17970
17102
  }
17103
+ getMaxBufferLength(mainBufferLength) {
17104
+ const maxConfigBuffer = super.getMaxBufferLength();
17105
+ if (!mainBufferLength) {
17106
+ return maxConfigBuffer;
17107
+ }
17108
+ return Math.max(maxConfigBuffer, mainBufferLength);
17109
+ }
17971
17110
  loadFragment(frag, level, targetBufferTime) {
17972
17111
  this.fragCurrent = frag;
17973
17112
  if (frag.sn === 'initSegment') {
@@ -18007,7 +17146,7 @@ class BufferableInstance {
18007
17146
 
18008
17147
  class SubtitleTrackController extends BasePlaylistController {
18009
17148
  constructor(hls) {
18010
- super(hls, 'subtitle-track-controller');
17149
+ super(hls, '[subtitle-track-controller]');
18011
17150
  this.media = null;
18012
17151
  this.tracks = [];
18013
17152
  this.groupIds = null;
@@ -18016,10 +17155,10 @@ class SubtitleTrackController extends BasePlaylistController {
18016
17155
  this.currentTrack = null;
18017
17156
  this.selectDefaultTrack = true;
18018
17157
  this.queuedDefaultTrack = -1;
17158
+ this.asyncPollTrackChange = () => this.pollTrackChange(0);
18019
17159
  this.useTextTrackPolling = false;
18020
17160
  this.subtitlePollingInterval = -1;
18021
17161
  this._subtitleDisplay = true;
18022
- this.asyncPollTrackChange = () => this.pollTrackChange(0);
18023
17162
  this.onTextTracksChanged = () => {
18024
17163
  if (!this.useTextTrackPolling) {
18025
17164
  self.clearInterval(this.subtitlePollingInterval);
@@ -18053,7 +17192,6 @@ class SubtitleTrackController extends BasePlaylistController {
18053
17192
  this.tracks.length = 0;
18054
17193
  this.tracksInGroup.length = 0;
18055
17194
  this.currentTrack = null;
18056
- // @ts-ignore
18057
17195
  this.onTextTracksChanged = this.asyncPollTrackChange = null;
18058
17196
  super.destroy();
18059
17197
  }
@@ -18465,22 +17603,24 @@ class BufferOperationQueue {
18465
17603
  this.executeNext(type);
18466
17604
  }
18467
17605
  }
17606
+ insertAbort(operation, type) {
17607
+ const queue = this.queues[type];
17608
+ queue.unshift(operation);
17609
+ this.executeNext(type);
17610
+ }
18468
17611
  appendBlocker(type) {
18469
- return new Promise(resolve => {
18470
- const operation = {
18471
- execute: resolve,
18472
- onStart: () => {},
18473
- onComplete: () => {},
18474
- onError: () => {}
18475
- };
18476
- this.append(operation, type);
17612
+ let execute;
17613
+ const promise = new Promise(resolve => {
17614
+ execute = resolve;
18477
17615
  });
18478
- }
18479
- unblockAudio(op) {
18480
- const queue = this.queues.audio;
18481
- if (queue[0] === op) {
18482
- this.shiftAndExecuteNext('audio');
18483
- }
17616
+ const operation = {
17617
+ execute,
17618
+ onStart: () => {},
17619
+ onComplete: () => {},
17620
+ onError: () => {}
17621
+ };
17622
+ this.append(operation, type);
17623
+ return promise;
18484
17624
  }
18485
17625
  executeNext(type) {
18486
17626
  const queue = this.queues[type];
@@ -18512,9 +17652,8 @@ class BufferOperationQueue {
18512
17652
  }
18513
17653
 
18514
17654
  const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
18515
- class BufferController extends Logger {
18516
- constructor(hls, fragmentTracker) {
18517
- super('buffer-controller', hls.logger);
17655
+ class BufferController {
17656
+ constructor(hls) {
18518
17657
  // The level details used to determine duration, target-duration and live
18519
17658
  this.details = null;
18520
17659
  // cache the self generated object url to detect hijack of video tag
@@ -18524,7 +17663,6 @@ class BufferController extends Logger {
18524
17663
  // References to event listeners for each SourceBuffer, so that they can be referenced for event removal
18525
17664
  this.listeners = void 0;
18526
17665
  this.hls = void 0;
18527
- this.fragmentTracker = void 0;
18528
17666
  // The number of BUFFER_CODEC events received before any sourceBuffers are created
18529
17667
  this.bufferCodecEventsExpected = 0;
18530
17668
  // The total number of BUFFER_CODEC events received
@@ -18535,10 +17673,6 @@ class BufferController extends Logger {
18535
17673
  this.mediaSource = null;
18536
17674
  // Last MP3 audio chunk appended
18537
17675
  this.lastMpegAudioChunk = null;
18538
- // Audio fragment blocked from appending until corresponding video appends or context changes
18539
- this.blockedAudioAppend = null;
18540
- // Keep track of video append position for unblocking audio
18541
- this.lastVideoAppendEnd = 0;
18542
17676
  this.appendSource = void 0;
18543
17677
  // counters
18544
17678
  this.appendErrors = {
@@ -18549,6 +17683,9 @@ class BufferController extends Logger {
18549
17683
  this.tracks = {};
18550
17684
  this.pendingTracks = {};
18551
17685
  this.sourceBuffer = void 0;
17686
+ this.log = void 0;
17687
+ this.warn = void 0;
17688
+ this.error = void 0;
18552
17689
  this._onEndStreaming = event => {
18553
17690
  if (!this.hls) {
18554
17691
  return;
@@ -18570,10 +17707,7 @@ class BufferController extends Logger {
18570
17707
  this.log('Media source opened');
18571
17708
  if (media) {
18572
17709
  media.removeEventListener('emptied', this._onMediaEmptied);
18573
- const durationAndRange = this.getDurationAndRange();
18574
- if (durationAndRange) {
18575
- this.updateMediaSource(durationAndRange);
18576
- }
17710
+ this.updateMediaElementDuration();
18577
17711
  this.hls.trigger(Events.MEDIA_ATTACHED, {
18578
17712
  media,
18579
17713
  mediaSource: mediaSource
@@ -18597,12 +17731,15 @@ class BufferController extends Logger {
18597
17731
  _objectUrl
18598
17732
  } = this;
18599
17733
  if (mediaSrc !== _objectUrl) {
18600
- this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
17734
+ logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
18601
17735
  }
18602
17736
  };
18603
17737
  this.hls = hls;
18604
- this.fragmentTracker = fragmentTracker;
18605
- this.appendSource = hls.config.preferManagedMediaSource;
17738
+ const logPrefix = '[buffer-controller]';
17739
+ this.appendSource = hls.config.preferManagedMediaSource && typeof self !== 'undefined' && self.ManagedMediaSource;
17740
+ this.log = logger.log.bind(logger, logPrefix);
17741
+ this.warn = logger.warn.bind(logger, logPrefix);
17742
+ this.error = logger.error.bind(logger, logPrefix);
18606
17743
  this._initSourceBuffer();
18607
17744
  this.registerListeners();
18608
17745
  }
@@ -18614,13 +17751,7 @@ class BufferController extends Logger {
18614
17751
  this.details = null;
18615
17752
  this.lastMpegAudioChunk = null;
18616
17753
  // @ts-ignore
18617
- this.hls = this.fragmentTracker = null;
18618
- // @ts-ignore
18619
- this._onMediaSourceOpen = this._onMediaSourceClose = null;
18620
- // @ts-ignore
18621
- this._onMediaSourceEnded = null;
18622
- // @ts-ignore
18623
- this._onStartStreaming = this._onEndStreaming = null;
17754
+ this.hls = null;
18624
17755
  }
18625
17756
  registerListeners() {
18626
17757
  const {
@@ -18670,8 +17801,6 @@ class BufferController extends Logger {
18670
17801
  audiovideo: 0
18671
17802
  };
18672
17803
  this.lastMpegAudioChunk = null;
18673
- this.blockedAudioAppend = null;
18674
- this.lastVideoAppendEnd = 0;
18675
17804
  }
18676
17805
  onManifestLoading() {
18677
17806
  this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = 0;
@@ -18700,8 +17829,10 @@ class BufferController extends Logger {
18700
17829
  ms.addEventListener('sourceopen', this._onMediaSourceOpen);
18701
17830
  ms.addEventListener('sourceended', this._onMediaSourceEnded);
18702
17831
  ms.addEventListener('sourceclose', this._onMediaSourceClose);
18703
- ms.addEventListener('startstreaming', this._onStartStreaming);
18704
- ms.addEventListener('endstreaming', this._onEndStreaming);
17832
+ if (this.appendSource) {
17833
+ ms.addEventListener('startstreaming', this._onStartStreaming);
17834
+ ms.addEventListener('endstreaming', this._onEndStreaming);
17835
+ }
18705
17836
 
18706
17837
  // cache the locally generated object url
18707
17838
  const objectUrl = this._objectUrl = self.URL.createObjectURL(ms);
@@ -18748,8 +17879,10 @@ class BufferController extends Logger {
18748
17879
  mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen);
18749
17880
  mediaSource.removeEventListener('sourceended', this._onMediaSourceEnded);
18750
17881
  mediaSource.removeEventListener('sourceclose', this._onMediaSourceClose);
18751
- mediaSource.removeEventListener('startstreaming', this._onStartStreaming);
18752
- mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
17882
+ if (this.appendSource) {
17883
+ mediaSource.removeEventListener('startstreaming', this._onStartStreaming);
17884
+ mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
17885
+ }
18753
17886
 
18754
17887
  // Detach properly the MediaSource from the HTMLMediaElement as
18755
17888
  // suggested in https://github.com/w3c/media-source/issues/53.
@@ -18785,7 +17918,6 @@ class BufferController extends Logger {
18785
17918
  this.resetBuffer(type);
18786
17919
  });
18787
17920
  this._initSourceBuffer();
18788
- this.hls.resumeBuffering();
18789
17921
  }
18790
17922
  resetBuffer(type) {
18791
17923
  const sb = this.sourceBuffer[type];
@@ -18809,10 +17941,9 @@ class BufferController extends Logger {
18809
17941
  const trackNames = Object.keys(data);
18810
17942
  trackNames.forEach(trackName => {
18811
17943
  if (sourceBufferCount) {
18812
- var _track$buffer;
18813
17944
  // check if SourceBuffer codec needs to change
18814
17945
  const track = this.tracks[trackName];
18815
- if (track && typeof ((_track$buffer = track.buffer) == null ? void 0 : _track$buffer.changeType) === 'function') {
17946
+ if (track && typeof track.buffer.changeType === 'function') {
18816
17947
  var _trackCodec;
18817
17948
  const {
18818
17949
  id,
@@ -18827,7 +17958,7 @@ class BufferController extends Logger {
18827
17958
  const nextCodec = (_trackCodec = trackCodec) == null ? void 0 : _trackCodec.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');
18828
17959
  if (trackCodec && currentCodec !== nextCodec) {
18829
17960
  if (trackName.slice(0, 5) === 'audio') {
18830
- trackCodec = getCodecCompatibleName(trackCodec, this.hls.config.preferManagedMediaSource);
17961
+ trackCodec = getCodecCompatibleName(trackCodec, this.appendSource);
18831
17962
  }
18832
17963
  const mimeType = `${container};codecs=${trackCodec}`;
18833
17964
  this.appendChangeType(trackName, mimeType);
@@ -18882,54 +18013,20 @@ class BufferController extends Logger {
18882
18013
  };
18883
18014
  operationQueue.append(operation, type, !!this.pendingTracks[type]);
18884
18015
  }
18885
- blockAudio(partOrFrag) {
18886
- var _this$fragmentTracker;
18887
- const pStart = partOrFrag.start;
18888
- const pTime = pStart + partOrFrag.duration * 0.05;
18889
- const atGap = ((_this$fragmentTracker = this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker.gap) === true;
18890
- if (atGap) {
18891
- return;
18892
- }
18893
- const op = {
18894
- execute: () => {
18895
- var _this$fragmentTracker2;
18896
- if (this.lastVideoAppendEnd > pTime || this.sourceBuffer.video && BufferHelper.isBuffered(this.sourceBuffer.video, pTime) || ((_this$fragmentTracker2 = this.fragmentTracker.getAppendedFrag(pTime, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker2.gap) === true) {
18897
- this.blockedAudioAppend = null;
18898
- this.operationQueue.shiftAndExecuteNext('audio');
18899
- }
18900
- },
18901
- onStart: () => {},
18902
- onComplete: () => {},
18903
- onError: () => {}
18904
- };
18905
- this.blockedAudioAppend = {
18906
- op,
18907
- frag: partOrFrag
18908
- };
18909
- this.operationQueue.append(op, 'audio', true);
18910
- }
18911
- unblockAudio() {
18912
- const blockedAudioAppend = this.blockedAudioAppend;
18913
- if (blockedAudioAppend) {
18914
- this.blockedAudioAppend = null;
18915
- this.operationQueue.unblockAudio(blockedAudioAppend.op);
18916
- }
18917
- }
18918
18016
  onBufferAppending(event, eventData) {
18919
18017
  const {
18018
+ hls,
18920
18019
  operationQueue,
18921
18020
  tracks
18922
18021
  } = this;
18923
18022
  const {
18924
18023
  data,
18925
18024
  type,
18926
- parent,
18927
18025
  frag,
18928
18026
  part,
18929
18027
  chunkMeta
18930
18028
  } = eventData;
18931
18029
  const chunkStats = chunkMeta.buffering[type];
18932
- const sn = frag.sn;
18933
18030
  const bufferAppendingStart = self.performance.now();
18934
18031
  chunkStats.start = bufferAppendingStart;
18935
18032
  const fragBuffering = frag.stats.buffering;
@@ -18952,36 +18049,7 @@ class BufferController extends Logger {
18952
18049
  checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn;
18953
18050
  this.lastMpegAudioChunk = chunkMeta;
18954
18051
  }
18955
-
18956
- // Block audio append until overlapping video append
18957
- const videoSb = this.sourceBuffer.video;
18958
- if (videoSb && sn !== 'initSegment') {
18959
- const partOrFrag = part || frag;
18960
- const blockedAudioAppend = this.blockedAudioAppend;
18961
- if (type === 'audio' && parent !== 'main' && !this.blockedAudioAppend) {
18962
- const pStart = partOrFrag.start;
18963
- const pTime = pStart + partOrFrag.duration * 0.05;
18964
- const vbuffered = videoSb.buffered;
18965
- const vappending = this.operationQueue.current('video');
18966
- if (!vbuffered.length && !vappending) {
18967
- // wait for video before appending audio
18968
- this.blockAudio(partOrFrag);
18969
- } else if (!vappending && !BufferHelper.isBuffered(videoSb, pTime) && this.lastVideoAppendEnd < pTime) {
18970
- // audio is ahead of video
18971
- this.blockAudio(partOrFrag);
18972
- }
18973
- } else if (type === 'video') {
18974
- const videoAppendEnd = partOrFrag.end;
18975
- if (blockedAudioAppend) {
18976
- const audioStart = blockedAudioAppend.frag.start;
18977
- if (videoAppendEnd > audioStart || videoAppendEnd < this.lastVideoAppendEnd || BufferHelper.isBuffered(videoSb, audioStart)) {
18978
- this.unblockAudio();
18979
- }
18980
- }
18981
- this.lastVideoAppendEnd = videoAppendEnd;
18982
- }
18983
- }
18984
- const fragStart = (part || frag).start;
18052
+ const fragStart = frag.start;
18985
18053
  const operation = {
18986
18054
  execute: () => {
18987
18055
  chunkStats.executeStart = self.performance.now();
@@ -18990,7 +18058,7 @@ class BufferController extends Logger {
18990
18058
  if (sb) {
18991
18059
  const delta = fragStart - sb.timestampOffset;
18992
18060
  if (Math.abs(delta) >= 0.1) {
18993
- this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`);
18061
+ this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${frag.sn})`);
18994
18062
  sb.timestampOffset = fragStart;
18995
18063
  }
18996
18064
  }
@@ -19057,21 +18125,22 @@ class BufferController extends Logger {
19057
18125
  /* with UHD content, we could get loop of quota exceeded error until
19058
18126
  browser is able to evict some data from sourcebuffer. Retrying can help recover.
19059
18127
  */
19060
- this.warn(`Failed ${appendErrorCount}/${this.hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
19061
- if (appendErrorCount >= this.hls.config.appendErrorMaxRetry) {
18128
+ this.warn(`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
18129
+ if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
19062
18130
  event.fatal = true;
19063
18131
  }
19064
18132
  }
19065
- this.hls.trigger(Events.ERROR, event);
18133
+ hls.trigger(Events.ERROR, event);
19066
18134
  }
19067
18135
  };
19068
18136
  operationQueue.append(operation, type, !!this.pendingTracks[type]);
19069
18137
  }
19070
- getFlushOp(type, start, end) {
19071
- return {
19072
- execute: () => {
19073
- this.removeExecutor(type, start, end);
19074
- },
18138
+ onBufferFlushing(event, data) {
18139
+ const {
18140
+ operationQueue
18141
+ } = this;
18142
+ const flushOperation = type => ({
18143
+ execute: this.removeExecutor.bind(this, type, data.startOffset, data.endOffset),
19075
18144
  onStart: () => {
19076
18145
  // logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
19077
18146
  },
@@ -19084,22 +18153,12 @@ class BufferController extends Logger {
19084
18153
  onError: error => {
19085
18154
  this.warn(`Failed to remove from ${type} SourceBuffer`, error);
19086
18155
  }
19087
- };
19088
- }
19089
- onBufferFlushing(event, data) {
19090
- const {
19091
- operationQueue
19092
- } = this;
19093
- const {
19094
- type,
19095
- startOffset,
19096
- endOffset
19097
- } = data;
19098
- if (type) {
19099
- operationQueue.append(this.getFlushOp(type, startOffset, endOffset), type);
18156
+ });
18157
+ if (data.type) {
18158
+ operationQueue.append(flushOperation(data.type), data.type);
19100
18159
  } else {
19101
- this.getSourceBufferTypes().forEach(sbType => {
19102
- operationQueue.append(this.getFlushOp(sbType, startOffset, endOffset), sbType);
18160
+ this.getSourceBufferTypes().forEach(type => {
18161
+ operationQueue.append(flushOperation(type), type);
19103
18162
  });
19104
18163
  }
19105
18164
  }
@@ -19146,9 +18205,6 @@ class BufferController extends Logger {
19146
18205
  // on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
19147
18206
  // an undefined data.type will mark all buffers as EOS.
19148
18207
  onBufferEos(event, data) {
19149
- if (data.type === 'video') {
19150
- this.unblockAudio();
19151
- }
19152
18208
  const ended = this.getSourceBufferTypes().reduce((acc, type) => {
19153
18209
  const sb = this.sourceBuffer[type];
19154
18210
  if (sb && (!data.type || data.type === type)) {
@@ -19191,14 +18247,10 @@ class BufferController extends Logger {
19191
18247
  return;
19192
18248
  }
19193
18249
  this.details = details;
19194
- const durationAndRange = this.getDurationAndRange();
19195
- if (!durationAndRange) {
19196
- return;
19197
- }
19198
18250
  if (this.getSourceBufferTypes().length) {
19199
- this.blockBuffers(() => this.updateMediaSource(durationAndRange));
18251
+ this.blockBuffers(this.updateMediaElementDuration.bind(this));
19200
18252
  } else {
19201
- this.updateMediaSource(durationAndRange);
18253
+ this.updateMediaElementDuration();
19202
18254
  }
19203
18255
  }
19204
18256
  trimBuffers() {
@@ -19303,9 +18355,9 @@ class BufferController extends Logger {
19303
18355
  * 'liveDurationInfinity` is set to `true`
19304
18356
  * More details: https://github.com/video-dev/hls.js/issues/355
19305
18357
  */
19306
- getDurationAndRange() {
18358
+ updateMediaElementDuration() {
19307
18359
  if (!this.details || !this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
19308
- return null;
18360
+ return;
19309
18361
  }
19310
18362
  const {
19311
18363
  details,
@@ -19319,41 +18371,25 @@ class BufferController extends Logger {
19319
18371
  if (details.live && hls.config.liveDurationInfinity) {
19320
18372
  // Override duration to Infinity
19321
18373
  mediaSource.duration = Infinity;
19322
- const len = details.fragments.length;
19323
- if (len && details.live && !!mediaSource.setLiveSeekableRange) {
19324
- const start = Math.max(0, details.fragments[0].start);
19325
- const end = Math.max(start, start + details.totalduration);
19326
- return {
19327
- duration: Infinity,
19328
- start,
19329
- end
19330
- };
19331
- }
19332
- return {
19333
- duration: Infinity
19334
- };
18374
+ this.updateSeekableRange(details);
19335
18375
  } else if (levelDuration > msDuration && levelDuration > mediaDuration || !isFiniteNumber(mediaDuration)) {
19336
- return {
19337
- duration: levelDuration
19338
- };
18376
+ // levelDuration was the last value we set.
18377
+ // not using mediaSource.duration as the browser may tweak this value
18378
+ // only update Media Source duration if its value increase, this is to avoid
18379
+ // flushing already buffered portion when switching between quality level
18380
+ this.log(`Updating Media Source duration to ${levelDuration.toFixed(3)}`);
18381
+ mediaSource.duration = levelDuration;
19339
18382
  }
19340
- return null;
19341
18383
  }
19342
- updateMediaSource({
19343
- duration,
19344
- start,
19345
- end
19346
- }) {
19347
- if (!this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
19348
- return;
19349
- }
19350
- if (isFiniteNumber(duration)) {
19351
- this.log(`Updating Media Source duration to ${duration.toFixed(3)}`);
19352
- }
19353
- this.mediaSource.duration = duration;
19354
- if (start !== undefined && end !== undefined) {
19355
- this.log(`Media Source duration is set to ${this.mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
19356
- this.mediaSource.setLiveSeekableRange(start, end);
18384
+ updateSeekableRange(levelDetails) {
18385
+ const mediaSource = this.mediaSource;
18386
+ const fragments = levelDetails.fragments;
18387
+ const len = fragments.length;
18388
+ if (len && levelDetails.live && mediaSource != null && mediaSource.setLiveSeekableRange) {
18389
+ const start = Math.max(0, fragments[0].start);
18390
+ const end = Math.max(start, start + levelDetails.totalduration);
18391
+ this.log(`Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
18392
+ mediaSource.setLiveSeekableRange(start, end);
19357
18393
  }
19358
18394
  }
19359
18395
  checkPendingTracks() {
@@ -19411,7 +18447,7 @@ class BufferController extends Logger {
19411
18447
  let codec = track.levelCodec || track.codec;
19412
18448
  if (codec) {
19413
18449
  if (trackName.slice(0, 5) === 'audio') {
19414
- codec = getCodecCompatibleName(codec, this.hls.config.preferManagedMediaSource);
18450
+ codec = getCodecCompatibleName(codec, this.appendSource);
19415
18451
  }
19416
18452
  }
19417
18453
  const mimeType = `${track.container};codecs=${codec}`;
@@ -19423,15 +18459,17 @@ class BufferController extends Logger {
19423
18459
  this.addBufferListener(sbName, 'updateend', this._onSBUpdateEnd);
19424
18460
  this.addBufferListener(sbName, 'error', this._onSBUpdateError);
19425
18461
  // ManagedSourceBuffer bufferedchange event
19426
- this.addBufferListener(sbName, 'bufferedchange', (type, event) => {
19427
- // If media was ejected check for a change. Added ranges are redundant with changes on 'updateend' event.
19428
- const removedRanges = event.removedRanges;
19429
- if (removedRanges != null && removedRanges.length) {
19430
- this.hls.trigger(Events.BUFFER_FLUSHED, {
19431
- type: trackName
19432
- });
19433
- }
19434
- });
18462
+ if (this.appendSource) {
18463
+ this.addBufferListener(sbName, 'bufferedchange', (type, event) => {
18464
+ // If media was ejected check for a change. Added ranges are redundant with changes on 'updateend' event.
18465
+ const removedRanges = event.removedRanges;
18466
+ if (removedRanges != null && removedRanges.length) {
18467
+ this.hls.trigger(Events.BUFFER_FLUSHED, {
18468
+ type: trackName
18469
+ });
18470
+ }
18471
+ });
18472
+ }
19435
18473
  this.tracks[trackName] = {
19436
18474
  buffer: sb,
19437
18475
  codec: codec,
@@ -19536,7 +18574,6 @@ class BufferController extends Logger {
19536
18574
  }
19537
18575
  return;
19538
18576
  }
19539
- sb.ending = false;
19540
18577
  sb.ended = false;
19541
18578
  sb.appendBuffer(data);
19542
18579
  }
@@ -19556,14 +18593,10 @@ class BufferController extends Logger {
19556
18593
 
19557
18594
  // logger.debug(`[buffer-controller]: Blocking ${buffers} SourceBuffer`);
19558
18595
  const blockingOperations = buffers.map(type => operationQueue.appendBlocker(type));
19559
- const audioBlocked = buffers.length > 1 && !!this.blockedAudioAppend;
19560
- if (audioBlocked) {
19561
- this.unblockAudio();
19562
- }
19563
- Promise.all(blockingOperations).then(result => {
18596
+ Promise.all(blockingOperations).then(() => {
19564
18597
  // logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
19565
18598
  onUnblocked();
19566
- buffers.forEach((type, i) => {
18599
+ buffers.forEach(type => {
19567
18600
  const sb = this.sourceBuffer[type];
19568
18601
  // Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
19569
18602
  // true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration)
@@ -21989,12 +21022,14 @@ class TimelineController {
21989
21022
  this.cea608Parser1 = this.cea608Parser2 = undefined;
21990
21023
  }
21991
21024
  initCea608Parsers() {
21992
- const channel1 = new OutputFilter(this, 'textTrack1');
21993
- const channel2 = new OutputFilter(this, 'textTrack2');
21994
- const channel3 = new OutputFilter(this, 'textTrack3');
21995
- const channel4 = new OutputFilter(this, 'textTrack4');
21996
- this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
21997
- this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
21025
+ if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) {
21026
+ const channel1 = new OutputFilter(this, 'textTrack1');
21027
+ const channel2 = new OutputFilter(this, 'textTrack2');
21028
+ const channel3 = new OutputFilter(this, 'textTrack3');
21029
+ const channel4 = new OutputFilter(this, 'textTrack4');
21030
+ this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
21031
+ this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
21032
+ }
21998
21033
  }
21999
21034
  addCues(trackName, startTime, endTime, screen, cueRanges) {
22000
21035
  // skip cues which overlap more than 50% with previously parsed time ranges
@@ -22232,7 +21267,7 @@ class TimelineController {
22232
21267
  if (inUseTracks != null && inUseTracks.length) {
22233
21268
  const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
22234
21269
  if (unusedTextTracks.length) {
22235
- this.hls.logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
21270
+ logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
22236
21271
  }
22237
21272
  }
22238
21273
  } else if (this.tracks.length) {
@@ -22277,23 +21312,26 @@ class TimelineController {
22277
21312
  return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
22278
21313
  }
22279
21314
  onFragLoading(event, data) {
21315
+ this.initCea608Parsers();
21316
+ const {
21317
+ cea608Parser1,
21318
+ cea608Parser2,
21319
+ lastCc,
21320
+ lastSn,
21321
+ lastPartIndex
21322
+ } = this;
21323
+ if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
21324
+ return;
21325
+ }
22280
21326
  // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
22281
- if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
21327
+ if (data.frag.type === PlaylistLevelType.MAIN) {
22282
21328
  var _data$part$index, _data$part;
22283
- const {
22284
- cea608Parser1,
22285
- cea608Parser2,
22286
- lastSn
22287
- } = this;
22288
- if (!cea608Parser1 || !cea608Parser2) {
22289
- return;
22290
- }
22291
21329
  const {
22292
21330
  cc,
22293
21331
  sn
22294
21332
  } = data.frag;
22295
- const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
22296
- if (!(sn === lastSn + 1 || sn === lastSn && partIndex === this.lastPartIndex + 1 || cc === this.lastCc)) {
21333
+ const partIndex = (_data$part$index = data == null ? void 0 : (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
21334
+ if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
22297
21335
  cea608Parser1.reset();
22298
21336
  cea608Parser2.reset();
22299
21337
  }
@@ -22350,7 +21388,7 @@ class TimelineController {
22350
21388
  frag: frag
22351
21389
  });
22352
21390
  }, error => {
22353
- hls.logger.log(`Failed to parse IMSC1: ${error}`);
21391
+ logger.log(`Failed to parse IMSC1: ${error}`);
22354
21392
  hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
22355
21393
  success: false,
22356
21394
  frag: frag,
@@ -22391,7 +21429,7 @@ class TimelineController {
22391
21429
  this._fallbackToIMSC1(frag, payload);
22392
21430
  }
22393
21431
  // Something went wrong while parsing. Trigger event with success false.
22394
- hls.logger.log(`Failed to parse VTT cue: ${error}`);
21432
+ logger.log(`Failed to parse VTT cue: ${error}`);
22395
21433
  if (missingInitPTS && maxAvCC > frag.cc) {
22396
21434
  return;
22397
21435
  }
@@ -22452,7 +21490,12 @@ class TimelineController {
22452
21490
  this.captionsTracks = {};
22453
21491
  }
22454
21492
  onFragParsingUserdata(event, data) {
22455
- if (!this.enabled || !this.config.enableCEA708Captions) {
21493
+ this.initCea608Parsers();
21494
+ const {
21495
+ cea608Parser1,
21496
+ cea608Parser2
21497
+ } = this;
21498
+ if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
22456
21499
  return;
22457
21500
  }
22458
21501
  const {
@@ -22467,12 +21510,9 @@ class TimelineController {
22467
21510
  for (let i = 0; i < samples.length; i++) {
22468
21511
  const ccBytes = samples[i].bytes;
22469
21512
  if (ccBytes) {
22470
- if (!this.cea608Parser1) {
22471
- this.initCea608Parsers();
22472
- }
22473
21513
  const ccdatas = this.extractCea608Data(ccBytes);
22474
- this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
22475
- this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
21514
+ cea608Parser1.addData(samples[i].pts, ccdatas[0]);
21515
+ cea608Parser2.addData(samples[i].pts, ccdatas[1]);
22476
21516
  }
22477
21517
  }
22478
21518
  }
@@ -22668,10 +21708,10 @@ class CapLevelController {
22668
21708
  const hls = this.hls;
22669
21709
  const maxLevel = this.getMaxLevel(levels.length - 1);
22670
21710
  if (maxLevel !== this.autoLevelCapping) {
22671
- hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
21711
+ logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
22672
21712
  }
22673
21713
  hls.autoLevelCapping = maxLevel;
22674
- if (hls.autoLevelEnabled && hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
21714
+ if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
22675
21715
  // if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch
22676
21716
  // usually happen when the user go to the fullscreen mode.
22677
21717
  this.streamController.nextLevelSwitch();
@@ -22846,10 +21886,10 @@ class FPSController {
22846
21886
  totalDroppedFrames: droppedFrames
22847
21887
  });
22848
21888
  if (droppedFPS > 0) {
22849
- // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
21889
+ // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
22850
21890
  if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
22851
21891
  let currentLevel = hls.currentLevel;
22852
- hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
21892
+ logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
22853
21893
  if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
22854
21894
  currentLevel = currentLevel - 1;
22855
21895
  hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
@@ -22881,6 +21921,7 @@ class FPSController {
22881
21921
  }
22882
21922
  }
22883
21923
 
21924
+ const LOGGER_PREFIX = '[eme]';
22884
21925
  /**
22885
21926
  * Controller to deal with encrypted media extensions (EME)
22886
21927
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
@@ -22888,9 +21929,8 @@ class FPSController {
22888
21929
  * @class
22889
21930
  * @constructor
22890
21931
  */
22891
- class EMEController extends Logger {
21932
+ class EMEController {
22892
21933
  constructor(hls) {
22893
- super('eme', hls.logger);
22894
21934
  this.hls = void 0;
22895
21935
  this.config = void 0;
22896
21936
  this.media = null;
@@ -22899,101 +21939,13 @@ class EMEController extends Logger {
22899
21939
  this._requestLicenseFailureCount = 0;
22900
21940
  this.mediaKeySessions = [];
22901
21941
  this.keyIdToKeySessionPromise = {};
22902
- this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
22903
- this.onMediaEncrypted = event => {
22904
- const {
22905
- initDataType,
22906
- initData
22907
- } = event;
22908
- this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22909
-
22910
- // Ignore event when initData is null
22911
- if (initData === null) {
22912
- return;
22913
- }
22914
- let keyId;
22915
- let keySystemDomain;
22916
- if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22917
- // Match sinf keyId to playlist skd://keyId=
22918
- const json = bin2str(new Uint8Array(initData));
22919
- try {
22920
- const sinf = base64Decode(JSON.parse(json).sinf);
22921
- const tenc = parseSinf(new Uint8Array(sinf));
22922
- if (!tenc) {
22923
- return;
22924
- }
22925
- keyId = tenc.subarray(8, 24);
22926
- keySystemDomain = KeySystems.FAIRPLAY;
22927
- } catch (error) {
22928
- this.warn('Failed to parse sinf "encrypted" event message initData');
22929
- return;
22930
- }
22931
- } else {
22932
- // Support clear-lead key-session creation (otherwise depend on playlist keys)
22933
- const psshInfo = parsePssh(initData);
22934
- if (psshInfo === null) {
22935
- return;
22936
- }
22937
- if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22938
- keyId = psshInfo.data.subarray(8, 24);
22939
- }
22940
- keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22941
- }
22942
- if (!keySystemDomain || !keyId) {
22943
- return;
22944
- }
22945
- const keyIdHex = Hex.hexDump(keyId);
22946
- const {
22947
- keyIdToKeySessionPromise,
22948
- mediaKeySessions
22949
- } = this;
22950
- let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22951
- for (let i = 0; i < mediaKeySessions.length; i++) {
22952
- // Match playlist key
22953
- const keyContext = mediaKeySessions[i];
22954
- const decryptdata = keyContext.decryptdata;
22955
- if (decryptdata.pssh || !decryptdata.keyId) {
22956
- continue;
22957
- }
22958
- const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22959
- if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22960
- keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22961
- delete keyIdToKeySessionPromise[oldKeyIdHex];
22962
- decryptdata.pssh = new Uint8Array(initData);
22963
- decryptdata.keyId = keyId;
22964
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22965
- return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22966
- });
22967
- break;
22968
- }
22969
- }
22970
- if (!keySessionContextPromise) {
22971
- // Clear-lead key (not encountered in playlist)
22972
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22973
- keySystem,
22974
- mediaKeys
22975
- }) => {
22976
- var _keySystemToKeySystem;
22977
- this.throwIfDestroyed();
22978
- const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22979
- decryptdata.pssh = new Uint8Array(initData);
22980
- decryptdata.keyId = keyId;
22981
- return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22982
- this.throwIfDestroyed();
22983
- const keySessionContext = this.createMediaKeySessionContext({
22984
- decryptdata,
22985
- keySystem,
22986
- mediaKeys
22987
- });
22988
- return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22989
- });
22990
- });
22991
- }
22992
- keySessionContextPromise.catch(error => this.handleError(error));
22993
- };
22994
- this.onWaitingForKey = event => {
22995
- this.log(`"${event.type}" event`);
22996
- };
21942
+ this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
21943
+ this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
21944
+ this.onWaitingForKey = this._onWaitingForKey.bind(this);
21945
+ this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
21946
+ this.log = logger.log.bind(logger, LOGGER_PREFIX);
21947
+ this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
21948
+ this.error = logger.error.bind(logger, LOGGER_PREFIX);
22997
21949
  this.hls = hls;
22998
21950
  this.config = hls.config;
22999
21951
  this.registerListeners();
@@ -23007,9 +21959,9 @@ class EMEController extends Logger {
23007
21959
  config.licenseXhrSetup = config.licenseResponseCallback = undefined;
23008
21960
  config.drmSystems = config.drmSystemOptions = {};
23009
21961
  // @ts-ignore
23010
- this.hls = this.config = this.keyIdToKeySessionPromise = null;
21962
+ this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
23011
21963
  // @ts-ignore
23012
- this.onMediaEncrypted = this.onWaitingForKey = null;
21964
+ this.config = null;
23013
21965
  }
23014
21966
  registerListeners() {
23015
21967
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -23273,6 +22225,100 @@ class EMEController extends Logger {
23273
22225
  }
23274
22226
  return this.attemptKeySystemAccess(keySystemsToAttempt);
23275
22227
  }
22228
+ _onMediaEncrypted(event) {
22229
+ const {
22230
+ initDataType,
22231
+ initData
22232
+ } = event;
22233
+ this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22234
+
22235
+ // Ignore event when initData is null
22236
+ if (initData === null) {
22237
+ return;
22238
+ }
22239
+ let keyId;
22240
+ let keySystemDomain;
22241
+ if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22242
+ // Match sinf keyId to playlist skd://keyId=
22243
+ const json = bin2str(new Uint8Array(initData));
22244
+ try {
22245
+ const sinf = base64Decode(JSON.parse(json).sinf);
22246
+ const tenc = parseSinf(new Uint8Array(sinf));
22247
+ if (!tenc) {
22248
+ return;
22249
+ }
22250
+ keyId = tenc.subarray(8, 24);
22251
+ keySystemDomain = KeySystems.FAIRPLAY;
22252
+ } catch (error) {
22253
+ this.warn('Failed to parse sinf "encrypted" event message initData');
22254
+ return;
22255
+ }
22256
+ } else {
22257
+ // Support clear-lead key-session creation (otherwise depend on playlist keys)
22258
+ const psshInfo = parsePssh(initData);
22259
+ if (psshInfo === null) {
22260
+ return;
22261
+ }
22262
+ if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22263
+ keyId = psshInfo.data.subarray(8, 24);
22264
+ }
22265
+ keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22266
+ }
22267
+ if (!keySystemDomain || !keyId) {
22268
+ return;
22269
+ }
22270
+ const keyIdHex = Hex.hexDump(keyId);
22271
+ const {
22272
+ keyIdToKeySessionPromise,
22273
+ mediaKeySessions
22274
+ } = this;
22275
+ let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22276
+ for (let i = 0; i < mediaKeySessions.length; i++) {
22277
+ // Match playlist key
22278
+ const keyContext = mediaKeySessions[i];
22279
+ const decryptdata = keyContext.decryptdata;
22280
+ if (decryptdata.pssh || !decryptdata.keyId) {
22281
+ continue;
22282
+ }
22283
+ const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22284
+ if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22285
+ keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22286
+ delete keyIdToKeySessionPromise[oldKeyIdHex];
22287
+ decryptdata.pssh = new Uint8Array(initData);
22288
+ decryptdata.keyId = keyId;
22289
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22290
+ return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22291
+ });
22292
+ break;
22293
+ }
22294
+ }
22295
+ if (!keySessionContextPromise) {
22296
+ // Clear-lead key (not encountered in playlist)
22297
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22298
+ keySystem,
22299
+ mediaKeys
22300
+ }) => {
22301
+ var _keySystemToKeySystem;
22302
+ this.throwIfDestroyed();
22303
+ const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22304
+ decryptdata.pssh = new Uint8Array(initData);
22305
+ decryptdata.keyId = keyId;
22306
+ return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22307
+ this.throwIfDestroyed();
22308
+ const keySessionContext = this.createMediaKeySessionContext({
22309
+ decryptdata,
22310
+ keySystem,
22311
+ mediaKeys
22312
+ });
22313
+ return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22314
+ });
22315
+ });
22316
+ }
22317
+ keySessionContextPromise.catch(error => this.handleError(error));
22318
+ }
22319
+ _onWaitingForKey(event) {
22320
+ this.log(`"${event.type}" event`);
22321
+ }
23276
22322
  attemptSetMediaKeys(keySystem, mediaKeys) {
23277
22323
  const queue = this.setMediaKeysQueue.slice();
23278
22324
  this.log(`Setting media-keys for "${keySystem}"`);
@@ -23865,6 +22911,20 @@ class SfItem {
23865
22911
  }
23866
22912
  }
23867
22913
 
22914
+ /**
22915
+ * A class to represent structured field tokens when `Symbol` is not available.
22916
+ *
22917
+ * @group Structured Field
22918
+ *
22919
+ * @beta
22920
+ */
22921
+ class SfToken {
22922
+ constructor(description) {
22923
+ this.description = void 0;
22924
+ this.description = description;
22925
+ }
22926
+ }
22927
+
23868
22928
  const DICT = 'Dict';
23869
22929
 
23870
22930
  function format(value) {
@@ -23888,27 +22948,29 @@ function throwError(action, src, type, cause) {
23888
22948
  });
23889
22949
  }
23890
22950
 
23891
- function serializeError(src, type, cause) {
23892
- return throwError('serialize', src, type, cause);
23893
- }
22951
+ const BARE_ITEM = 'Bare Item';
23894
22952
 
23895
- /**
23896
- * A class to represent structured field tokens when `Symbol` is not available.
23897
- *
23898
- * @group Structured Field
23899
- *
23900
- * @beta
23901
- */
23902
- class SfToken {
23903
- constructor(description) {
23904
- this.description = void 0;
23905
- this.description = description;
23906
- }
22953
+ const BOOLEAN = 'Boolean';
22954
+
22955
+ const BYTES = 'Byte Sequence';
22956
+
22957
+ const DECIMAL = 'Decimal';
22958
+
22959
+ const INTEGER = 'Integer';
22960
+
22961
+ function isInvalidInt(value) {
22962
+ return value < -999999999999999 || 999999999999999 < value;
23907
22963
  }
23908
22964
 
23909
- const BARE_ITEM = 'Bare Item';
22965
+ const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
23910
22966
 
23911
- const BOOLEAN = 'Boolean';
22967
+ const TOKEN = 'Token';
22968
+
22969
+ const KEY = 'Key';
22970
+
22971
+ function serializeError(src, type, cause) {
22972
+ return throwError('serialize', src, type, cause);
22973
+ }
23912
22974
 
23913
22975
  // 4.1.9. Serializing a Boolean
23914
22976
  //
@@ -23947,8 +23009,6 @@ function base64encode(binary) {
23947
23009
  return btoa(String.fromCharCode(...binary));
23948
23010
  }
23949
23011
 
23950
- const BYTES = 'Byte Sequence';
23951
-
23952
23012
  // 4.1.8. Serializing a Byte Sequence
23953
23013
  //
23954
23014
  // Given a Byte Sequence as input_bytes, return an ASCII string suitable
@@ -23980,12 +23040,6 @@ function serializeByteSequence(value) {
23980
23040
  return `:${base64encode(value)}:`;
23981
23041
  }
23982
23042
 
23983
- const INTEGER = 'Integer';
23984
-
23985
- function isInvalidInt(value) {
23986
- return value < -999999999999999 || 999999999999999 < value;
23987
- }
23988
-
23989
23043
  // 4.1.4. Serializing an Integer
23990
23044
  //
23991
23045
  // Given an Integer as input_integer, return an ASCII string suitable
@@ -24051,8 +23105,6 @@ function roundToEven(value, precision) {
24051
23105
  }
24052
23106
  }
24053
23107
 
24054
- const DECIMAL = 'Decimal';
24055
-
24056
23108
  // 4.1.5. Serializing a Decimal
24057
23109
  //
24058
23110
  // Given a decimal number as input_decimal, return an ASCII string
@@ -24098,8 +23150,6 @@ function serializeDecimal(value) {
24098
23150
 
24099
23151
  const STRING = 'String';
24100
23152
 
24101
- const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
24102
-
24103
23153
  // 4.1.6. Serializing a String
24104
23154
  //
24105
23155
  // Given a String as input_string, return an ASCII string suitable for
@@ -24135,8 +23185,6 @@ function symbolToStr(symbol) {
24135
23185
  return symbol.description || symbol.toString().slice(7, -1);
24136
23186
  }
24137
23187
 
24138
- const TOKEN = 'Token';
24139
-
24140
23188
  function serializeToken(token) {
24141
23189
  const value = symbolToStr(token);
24142
23190
  if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
@@ -24204,8 +23252,6 @@ function serializeBareItem(value) {
24204
23252
  }
24205
23253
  }
24206
23254
 
24207
- const KEY = 'Key';
24208
-
24209
23255
  // 4.1.1.3. Serializing a Key
24210
23256
  //
24211
23257
  // Given a key as input_key, return an ASCII string suitable for use in
@@ -24447,6 +23493,36 @@ function urlToRelativePath(url, base) {
24447
23493
  return toPath.join('/');
24448
23494
  }
24449
23495
 
23496
+ /**
23497
+ * Generate a random v4 UUID
23498
+ *
23499
+ * @returns A random v4 UUID
23500
+ *
23501
+ * @group Utils
23502
+ *
23503
+ * @beta
23504
+ */
23505
+ function uuid() {
23506
+ try {
23507
+ return crypto.randomUUID();
23508
+ } catch (error) {
23509
+ try {
23510
+ const url = URL.createObjectURL(new Blob());
23511
+ const uuid = url.toString();
23512
+ URL.revokeObjectURL(url);
23513
+ return uuid.slice(uuid.lastIndexOf('/') + 1);
23514
+ } catch (error) {
23515
+ let dt = new Date().getTime();
23516
+ const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
23517
+ const r = (dt + Math.random() * 16) % 16 | 0;
23518
+ dt = Math.floor(dt / 16);
23519
+ return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
23520
+ });
23521
+ return uuid;
23522
+ }
23523
+ }
23524
+ }
23525
+
24450
23526
  const toRounded = value => Math.round(value);
24451
23527
  const toUrlSafe = (value, options) => {
24452
23528
  if (options != null && options.baseUrl) {
@@ -24672,36 +23748,6 @@ function appendCmcdQuery(url, cmcd, options) {
24672
23748
  return `${url}${separator}${query}`;
24673
23749
  }
24674
23750
 
24675
- /**
24676
- * Generate a random v4 UUID
24677
- *
24678
- * @returns A random v4 UUID
24679
- *
24680
- * @group Utils
24681
- *
24682
- * @beta
24683
- */
24684
- function uuid() {
24685
- try {
24686
- return crypto.randomUUID();
24687
- } catch (error) {
24688
- try {
24689
- const url = URL.createObjectURL(new Blob());
24690
- const uuid = url.toString();
24691
- URL.revokeObjectURL(url);
24692
- return uuid.slice(uuid.lastIndexOf('/') + 1);
24693
- } catch (error) {
24694
- let dt = new Date().getTime();
24695
- const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
24696
- const r = (dt + Math.random() * 16) % 16 | 0;
24697
- dt = Math.floor(dt / 16);
24698
- return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
24699
- });
24700
- return uuid;
24701
- }
24702
- }
24703
- }
24704
-
24705
23751
  /**
24706
23752
  * Controller to deal with Common Media Client Data (CMCD)
24707
23753
  * @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
@@ -24745,7 +23791,7 @@ class CMCDController {
24745
23791
  su: !this.initialized
24746
23792
  });
24747
23793
  } catch (error) {
24748
- this.hls.logger.warn('Could not generate manifest CMCD data.', error);
23794
+ logger.warn('Could not generate manifest CMCD data.', error);
24749
23795
  }
24750
23796
  };
24751
23797
  /**
@@ -24765,15 +23811,9 @@ class CMCDController {
24765
23811
  data.tb = this.getTopBandwidth(ot) / 1000;
24766
23812
  data.bl = this.getBufferLength(ot);
24767
23813
  }
24768
- const next = this.getNextFrag(fragment);
24769
- if (next) {
24770
- if (next.url && next.url !== fragment.url) {
24771
- data.nor = next.url;
24772
- }
24773
- }
24774
23814
  this.apply(context, data);
24775
23815
  } catch (error) {
24776
- this.hls.logger.warn('Could not generate segment CMCD data.', error);
23816
+ logger.warn('Could not generate segment CMCD data.', error);
24777
23817
  }
24778
23818
  };
24779
23819
  this.hls = hls;
@@ -24863,7 +23903,7 @@ class CMCDController {
24863
23903
  data.su = this.buffering;
24864
23904
  }
24865
23905
 
24866
- // TODO: Implement rtp, nrr, dl
23906
+ // TODO: Implement rtp, nrr, nor, dl
24867
23907
 
24868
23908
  const {
24869
23909
  includeKeys
@@ -24874,28 +23914,15 @@ class CMCDController {
24874
23914
  return acc;
24875
23915
  }, {});
24876
23916
  }
24877
- const options = {
24878
- baseUrl: context.url
24879
- };
24880
23917
  if (this.useHeaders) {
24881
23918
  if (!context.headers) {
24882
23919
  context.headers = {};
24883
23920
  }
24884
- appendCmcdHeaders(context.headers, data, options);
23921
+ appendCmcdHeaders(context.headers, data);
24885
23922
  } else {
24886
- context.url = appendCmcdQuery(context.url, data, options);
23923
+ context.url = appendCmcdQuery(context.url, data);
24887
23924
  }
24888
23925
  }
24889
- getNextFrag(fragment) {
24890
- var _this$hls$levels$frag;
24891
- const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
24892
- if (levelDetails) {
24893
- const index = fragment.sn - levelDetails.startSN;
24894
- return levelDetails.fragments[index + 1];
24895
- }
24896
- return undefined;
24897
- }
24898
-
24899
23926
  /**
24900
23927
  * The CMCD object type.
24901
23928
  */
@@ -25024,10 +24051,10 @@ class CMCDController {
25024
24051
  }
25025
24052
 
25026
24053
  const PATHWAY_PENALTY_DURATION_MS = 300000;
25027
- class ContentSteeringController extends Logger {
24054
+ class ContentSteeringController {
25028
24055
  constructor(hls) {
25029
- super('content-steering', hls.logger);
25030
24056
  this.hls = void 0;
24057
+ this.log = void 0;
25031
24058
  this.loader = null;
25032
24059
  this.uri = null;
25033
24060
  this.pathwayId = '.';
@@ -25042,6 +24069,7 @@ class ContentSteeringController extends Logger {
25042
24069
  this.subtitleTracks = null;
25043
24070
  this.penalizedPathways = {};
25044
24071
  this.hls = hls;
24072
+ this.log = logger.log.bind(logger, `[content-steering]:`);
25045
24073
  this.registerListeners();
25046
24074
  }
25047
24075
  registerListeners() {
@@ -25165,7 +24193,7 @@ class ContentSteeringController extends Logger {
25165
24193
  errorAction.resolved = this.pathwayId !== errorPathway;
25166
24194
  }
25167
24195
  if (!errorAction.resolved) {
25168
- 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)}`);
24196
+ 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)}`);
25169
24197
  }
25170
24198
  }
25171
24199
  }
@@ -25336,7 +24364,7 @@ class ContentSteeringController extends Logger {
25336
24364
  onSuccess: (response, stats, context, networkDetails) => {
25337
24365
  this.log(`Loaded steering manifest: "${url}"`);
25338
24366
  const steeringData = response.data;
25339
- if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
24367
+ if (steeringData.VERSION !== 1) {
25340
24368
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
25341
24369
  return;
25342
24370
  }
@@ -26306,7 +25334,7 @@ function timelineConfig() {
26306
25334
  /**
26307
25335
  * @ignore
26308
25336
  */
26309
- function mergeConfig(defaultConfig, userConfig, logger) {
25337
+ function mergeConfig(defaultConfig, userConfig) {
26310
25338
  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
26311
25339
  throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
26312
25340
  }
@@ -26376,7 +25404,7 @@ function deepCpy(obj) {
26376
25404
  /**
26377
25405
  * @ignore
26378
25406
  */
26379
- function enableStreamingMode(config, logger) {
25407
+ function enableStreamingMode(config) {
26380
25408
  const currentLoader = config.loader;
26381
25409
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
26382
25410
  // If a developer has configured their own loader, respect that choice
@@ -26393,9 +25421,10 @@ function enableStreamingMode(config, logger) {
26393
25421
  }
26394
25422
  }
26395
25423
 
25424
+ let chromeOrFirefox;
26396
25425
  class LevelController extends BasePlaylistController {
26397
25426
  constructor(hls, contentSteeringController) {
26398
- super(hls, 'level-controller');
25427
+ super(hls, '[level-controller]');
26399
25428
  this._levels = [];
26400
25429
  this._firstLevel = -1;
26401
25430
  this._maxAutoLevel = -1;
@@ -26466,15 +25495,23 @@ class LevelController extends BasePlaylistController {
26466
25495
  let videoCodecFound = false;
26467
25496
  let audioCodecFound = false;
26468
25497
  data.levels.forEach(levelParsed => {
26469
- var _videoCodec;
25498
+ var _audioCodec, _videoCodec;
26470
25499
  const attributes = levelParsed.attrs;
25500
+
25501
+ // erase audio codec info if browser does not support mp4a.40.34.
25502
+ // demuxer will autodetect codec and fallback to mpeg/audio
26471
25503
  let {
26472
25504
  audioCodec,
26473
25505
  videoCodec
26474
25506
  } = levelParsed;
25507
+ if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
25508
+ chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
25509
+ if (chromeOrFirefox) {
25510
+ levelParsed.audioCodec = audioCodec = undefined;
25511
+ }
25512
+ }
26475
25513
  if (audioCodec) {
26476
- // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
26477
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
25514
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
26478
25515
  }
26479
25516
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
26480
25517
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -26816,12 +25853,7 @@ class LevelController extends BasePlaylistController {
26816
25853
  if (curLevel.fragmentError === 0) {
26817
25854
  curLevel.loadError = 0;
26818
25855
  }
26819
- // Ignore matching details populated by loading a Media Playlist directly
26820
- let previousDetails = curLevel.details;
26821
- if (previousDetails === data.details && previousDetails.advanced) {
26822
- previousDetails = undefined;
26823
- }
26824
- this.playlistLoaded(level, data, previousDetails);
25856
+ this.playlistLoaded(level, data, curLevel.details);
26825
25857
  } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
26826
25858
  // received a delta playlist update that cannot be merged
26827
25859
  details.deltaUpdateFailed = true;
@@ -27065,8 +26097,6 @@ class KeyLoader {
27065
26097
  }
27066
26098
  return this.loadKeyEME(keyInfo, frag);
27067
26099
  case 'AES-128':
27068
- case 'AES-256':
27069
- case 'AES-256-CTR':
27070
26100
  return this.loadKeyHTTP(keyInfo, frag);
27071
26101
  default:
27072
26102
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -27204,9 +26234,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
27204
26234
  const MAX_START_GAP_JUMP = 2.0;
27205
26235
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
27206
26236
  const SKIP_BUFFER_RANGE_START = 0.05;
27207
- class GapController extends Logger {
26237
+ class GapController {
27208
26238
  constructor(config, media, fragmentTracker, hls) {
27209
- super('gap-controller', hls.logger);
27210
26239
  this.config = void 0;
27211
26240
  this.media = null;
27212
26241
  this.fragmentTracker = void 0;
@@ -27216,7 +26245,6 @@ class GapController extends Logger {
27216
26245
  this.stalled = null;
27217
26246
  this.moved = false;
27218
26247
  this.seeking = false;
27219
- this.ended = 0;
27220
26248
  this.config = config;
27221
26249
  this.media = media;
27222
26250
  this.fragmentTracker = fragmentTracker;
@@ -27234,7 +26262,7 @@ class GapController extends Logger {
27234
26262
  *
27235
26263
  * @param lastCurrentTime - Previously read playhead position
27236
26264
  */
27237
- poll(lastCurrentTime, activeFrag, levelDetails, state) {
26265
+ poll(lastCurrentTime, activeFrag) {
27238
26266
  const {
27239
26267
  config,
27240
26268
  media,
@@ -27253,7 +26281,6 @@ class GapController extends Logger {
27253
26281
 
27254
26282
  // The playhead is moving, no-op
27255
26283
  if (currentTime !== lastCurrentTime) {
27256
- this.ended = 0;
27257
26284
  this.moved = true;
27258
26285
  if (!seeking) {
27259
26286
  this.nudgeRetry = 0;
@@ -27262,7 +26289,7 @@ class GapController extends Logger {
27262
26289
  // The playhead is now moving, but was previously stalled
27263
26290
  if (this.stallReported) {
27264
26291
  const _stalledDuration = self.performance.now() - stalled;
27265
- this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
26292
+ logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
27266
26293
  this.stallReported = false;
27267
26294
  }
27268
26295
  this.stalled = null;
@@ -27298,6 +26325,7 @@ class GapController extends Logger {
27298
26325
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
27299
26326
  // The addition poll gives the browser a chance to jump the gap for us
27300
26327
  if (!this.moved && this.stalled !== null) {
26328
+ var _level$details;
27301
26329
  // There is no playable buffer (seeked, waiting for buffer)
27302
26330
  const isBuffered = bufferInfo.len > 0;
27303
26331
  if (!isBuffered && !nextStart) {
@@ -27309,8 +26337,9 @@ class GapController extends Logger {
27309
26337
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
27310
26338
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
27311
26339
  // that begins over 1 target duration after the video start position.
27312
- const isLive = !!(levelDetails != null && levelDetails.live);
27313
- const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
26340
+ const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
26341
+ const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
26342
+ const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
27314
26343
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
27315
26344
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
27316
26345
  if (!media.paused) {
@@ -27328,17 +26357,6 @@ class GapController extends Logger {
27328
26357
  }
27329
26358
  const stalledDuration = tnow - stalled;
27330
26359
  if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
27331
- // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
27332
- if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
27333
- if (stalledDuration < 1000 || this.ended) {
27334
- return;
27335
- }
27336
- this.ended = currentTime;
27337
- this.hls.trigger(Events.MEDIA_ENDED, {
27338
- stalled: true
27339
- });
27340
- return;
27341
- }
27342
26360
  // Report stalling after trying to fix
27343
26361
  this._reportStall(bufferInfo);
27344
26362
  if (!this.media) {
@@ -27382,7 +26400,7 @@ class GapController extends Logger {
27382
26400
  // needs to cross some sort of threshold covering all source-buffers content
27383
26401
  // to start playing properly.
27384
26402
  if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
27385
- this.warn('Trying to nudge playhead over buffer-hole');
26403
+ logger.warn('Trying to nudge playhead over buffer-hole');
27386
26404
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
27387
26405
  // We only try to jump the hole if it's under the configured size
27388
26406
  // Reset stalled so to rearm watchdog timer
@@ -27406,7 +26424,7 @@ class GapController extends Logger {
27406
26424
  // Report stalled error once
27407
26425
  this.stallReported = true;
27408
26426
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
27409
- this.warn(error.message);
26427
+ logger.warn(error.message);
27410
26428
  hls.trigger(Events.ERROR, {
27411
26429
  type: ErrorTypes.MEDIA_ERROR,
27412
26430
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -27474,7 +26492,7 @@ class GapController extends Logger {
27474
26492
  }
27475
26493
  }
27476
26494
  const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
27477
- this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
26495
+ logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
27478
26496
  this.moved = true;
27479
26497
  this.stalled = null;
27480
26498
  media.currentTime = targetTime;
@@ -27515,7 +26533,7 @@ class GapController extends Logger {
27515
26533
  const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
27516
26534
  // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
27517
26535
  const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
27518
- this.warn(error.message);
26536
+ logger.warn(error.message);
27519
26537
  media.currentTime = targetTime;
27520
26538
  hls.trigger(Events.ERROR, {
27521
26539
  type: ErrorTypes.MEDIA_ERROR,
@@ -27525,7 +26543,7 @@ class GapController extends Logger {
27525
26543
  });
27526
26544
  } else {
27527
26545
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
27528
- this.error(error.message);
26546
+ logger.error(error.message);
27529
26547
  hls.trigger(Events.ERROR, {
27530
26548
  type: ErrorTypes.MEDIA_ERROR,
27531
26549
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -27540,7 +26558,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
27540
26558
 
27541
26559
  class StreamController extends BaseStreamController {
27542
26560
  constructor(hls, fragmentTracker, keyLoader) {
27543
- super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
26561
+ super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
27544
26562
  this.audioCodecSwap = false;
27545
26563
  this.gapController = null;
27546
26564
  this.level = -1;
@@ -27548,43 +26566,27 @@ class StreamController extends BaseStreamController {
27548
26566
  this.altAudio = false;
27549
26567
  this.audioOnly = false;
27550
26568
  this.fragPlaying = null;
26569
+ this.onvplaying = null;
26570
+ this.onvseeked = null;
27551
26571
  this.fragLastKbps = 0;
27552
26572
  this.couldBacktrack = false;
27553
26573
  this.backtrackFragment = null;
27554
26574
  this.audioCodecSwitch = false;
27555
26575
  this.videoBuffer = null;
27556
- this.onMediaPlaying = () => {
27557
- // tick to speed up FRAG_CHANGED triggering
27558
- this.tick();
27559
- };
27560
- this.onMediaSeeked = () => {
27561
- const media = this.media;
27562
- const currentTime = media ? media.currentTime : null;
27563
- if (isFiniteNumber(currentTime)) {
27564
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
27565
- }
27566
-
27567
- // If seeked was issued before buffer was appended do not tick immediately
27568
- const bufferInfo = this.getMainFwdBufferInfo();
27569
- if (bufferInfo === null || bufferInfo.len === 0) {
27570
- this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
27571
- return;
27572
- }
27573
-
27574
- // tick to speed up FRAG_CHANGED triggering
27575
- this.tick();
27576
- };
27577
- this.registerListeners();
26576
+ this._registerListeners();
27578
26577
  }
27579
- registerListeners() {
27580
- super.registerListeners();
26578
+ _registerListeners() {
27581
26579
  const {
27582
26580
  hls
27583
26581
  } = this;
26582
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
26583
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
26584
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
27584
26585
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
27585
26586
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
27586
26587
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
27587
26588
  hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26589
+ hls.on(Events.ERROR, this.onError, this);
27588
26590
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
27589
26591
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
27590
26592
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -27592,14 +26594,17 @@ class StreamController extends BaseStreamController {
27592
26594
  hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
27593
26595
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
27594
26596
  }
27595
- unregisterListeners() {
27596
- super.unregisterListeners();
26597
+ _unregisterListeners() {
27597
26598
  const {
27598
26599
  hls
27599
26600
  } = this;
26601
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
26602
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
26603
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
27600
26604
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
27601
26605
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
27602
26606
  hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26607
+ hls.off(Events.ERROR, this.onError, this);
27603
26608
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
27604
26609
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
27605
26610
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -27608,9 +26613,7 @@ class StreamController extends BaseStreamController {
27608
26613
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
27609
26614
  }
27610
26615
  onHandlerDestroying() {
27611
- // @ts-ignore
27612
- this.onMediaPlaying = this.onMediaSeeked = null;
27613
- this.unregisterListeners();
26616
+ this._unregisterListeners();
27614
26617
  super.onHandlerDestroying();
27615
26618
  }
27616
26619
  startLoad(startPosition) {
@@ -27708,9 +26711,6 @@ class StreamController extends BaseStreamController {
27708
26711
  this.checkFragmentChanged();
27709
26712
  }
27710
26713
  doTickIdle() {
27711
- if (!this.buffering) {
27712
- return;
27713
- }
27714
26714
  const {
27715
26715
  hls,
27716
26716
  levelLastLoaded,
@@ -27938,17 +26938,20 @@ class StreamController extends BaseStreamController {
27938
26938
  onMediaAttached(event, data) {
27939
26939
  super.onMediaAttached(event, data);
27940
26940
  const media = data.media;
27941
- media.addEventListener('playing', this.onMediaPlaying);
27942
- media.addEventListener('seeked', this.onMediaSeeked);
26941
+ this.onvplaying = this.onMediaPlaying.bind(this);
26942
+ this.onvseeked = this.onMediaSeeked.bind(this);
26943
+ media.addEventListener('playing', this.onvplaying);
26944
+ media.addEventListener('seeked', this.onvseeked);
27943
26945
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
27944
26946
  }
27945
26947
  onMediaDetaching() {
27946
26948
  const {
27947
26949
  media
27948
26950
  } = this;
27949
- if (media) {
27950
- media.removeEventListener('playing', this.onMediaPlaying);
27951
- media.removeEventListener('seeked', this.onMediaSeeked);
26951
+ if (media && this.onvplaying && this.onvseeked) {
26952
+ media.removeEventListener('playing', this.onvplaying);
26953
+ media.removeEventListener('seeked', this.onvseeked);
26954
+ this.onvplaying = this.onvseeked = null;
27952
26955
  this.videoBuffer = null;
27953
26956
  }
27954
26957
  this.fragPlaying = null;
@@ -27958,6 +26961,27 @@ class StreamController extends BaseStreamController {
27958
26961
  }
27959
26962
  super.onMediaDetaching();
27960
26963
  }
26964
+ onMediaPlaying() {
26965
+ // tick to speed up FRAG_CHANGED triggering
26966
+ this.tick();
26967
+ }
26968
+ onMediaSeeked() {
26969
+ const media = this.media;
26970
+ const currentTime = media ? media.currentTime : null;
26971
+ if (isFiniteNumber(currentTime)) {
26972
+ this.log(`Media seeked to ${currentTime.toFixed(3)}`);
26973
+ }
26974
+
26975
+ // If seeked was issued before buffer was appended do not tick immediately
26976
+ const bufferInfo = this.getMainFwdBufferInfo();
26977
+ if (bufferInfo === null || bufferInfo.len === 0) {
26978
+ this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
26979
+ return;
26980
+ }
26981
+
26982
+ // tick to speed up FRAG_CHANGED triggering
26983
+ this.tick();
26984
+ }
27961
26985
  onManifestLoading() {
27962
26986
  // reset buffer on manifest loading
27963
26987
  this.log('Trigger BUFFER_RESET');
@@ -28249,10 +27273,8 @@ class StreamController extends BaseStreamController {
28249
27273
  }
28250
27274
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
28251
27275
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
28252
- const state = this.state;
28253
- const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
28254
- const levelDetails = this.getLevelDetails();
28255
- gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
27276
+ const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
27277
+ gapController.poll(this.lastCurrentTime, activeFrag);
28256
27278
  }
28257
27279
  this.lastCurrentTime = media.currentTime;
28258
27280
  }
@@ -28585,17 +27607,6 @@ class StreamController extends BaseStreamController {
28585
27607
  getMainFwdBufferInfo() {
28586
27608
  return this.getFwdBufferInfo(this.mediaBuffer ? this.mediaBuffer : this.media, PlaylistLevelType.MAIN);
28587
27609
  }
28588
- get maxBufferLength() {
28589
- const {
28590
- levels,
28591
- level
28592
- } = this;
28593
- const levelInfo = levels == null ? void 0 : levels[level];
28594
- if (!levelInfo) {
28595
- return this.config.maxBufferLength;
28596
- }
28597
- return this.getMaxBufferLength(levelInfo.maxBitrate);
28598
- }
28599
27610
  backtrack(frag) {
28600
27611
  this.couldBacktrack = true;
28601
27612
  // Causes findFragments to backtrack through fragments to find the keyframe
@@ -28701,7 +27712,7 @@ class Hls {
28701
27712
  * Get the video-dev/hls.js package version.
28702
27713
  */
28703
27714
  static get version() {
28704
- return "1.5.7-0.canary.10040";
27715
+ return "1.5.7";
28705
27716
  }
28706
27717
 
28707
27718
  /**
@@ -28764,12 +27775,9 @@ class Hls {
28764
27775
  * The configuration object provided on player instantiation.
28765
27776
  */
28766
27777
  this.userConfig = void 0;
28767
- /**
28768
- * The logger functions used by this player instance, configured on player instantiation.
28769
- */
28770
- this.logger = void 0;
28771
27778
  this.coreComponents = void 0;
28772
27779
  this.networkControllers = void 0;
27780
+ this.started = false;
28773
27781
  this._emitter = new EventEmitter();
28774
27782
  this._autoLevelCapping = -1;
28775
27783
  this._maxHdcpLevel = null;
@@ -28786,11 +27794,11 @@ class Hls {
28786
27794
  this._media = null;
28787
27795
  this.url = null;
28788
27796
  this.triggeringException = void 0;
28789
- const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
28790
- const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
27797
+ enableLogs(userConfig.debug || false, 'Hls instance');
27798
+ const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
28791
27799
  this.userConfig = userConfig;
28792
27800
  if (config.progressive) {
28793
- enableStreamingMode(config, logger);
27801
+ enableStreamingMode(config);
28794
27802
  }
28795
27803
 
28796
27804
  // core controllers and network loaders
@@ -28803,9 +27811,7 @@ class Hls {
28803
27811
  } = config;
28804
27812
  const errorController = new ConfigErrorController(this);
28805
27813
  const abrController = this.abrController = new ConfigAbrController(this);
28806
- // FragmentTracker must be defined before StreamController because the order of event handling is important
28807
- const fragmentTracker = new FragmentTracker(this);
28808
- const bufferController = this.bufferController = new ConfigBufferController(this, fragmentTracker);
27814
+ const bufferController = this.bufferController = new ConfigBufferController(this);
28809
27815
  const capLevelController = this.capLevelController = new ConfigCapLevelController(this);
28810
27816
  const fpsController = new ConfigFpsController(this);
28811
27817
  const playListLoader = new PlaylistLoader(this);
@@ -28814,6 +27820,8 @@ class Hls {
28814
27820
  // ConentSteeringController is defined before LevelController to receive Multivariant Playlist events first
28815
27821
  const contentSteering = ConfigContentSteeringController ? new ConfigContentSteeringController(this) : null;
28816
27822
  const levelController = this.levelController = new LevelController(this, contentSteering);
27823
+ // FragmentTracker must be defined before StreamController because the order of event handling is important
27824
+ const fragmentTracker = new FragmentTracker(this);
28817
27825
  const keyLoader = new KeyLoader(this.config);
28818
27826
  const streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader);
28819
27827
 
@@ -28889,7 +27897,7 @@ class Hls {
28889
27897
  try {
28890
27898
  return this.emit(event, event, eventObject);
28891
27899
  } catch (error) {
28892
- this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
27900
+ logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
28893
27901
  // Prevent recursion in error event handlers that throw #5497
28894
27902
  if (!this.triggeringException) {
28895
27903
  this.triggeringException = true;
@@ -28915,7 +27923,7 @@ class Hls {
28915
27923
  * Dispose of the instance
28916
27924
  */
28917
27925
  destroy() {
28918
- this.logger.log('destroy');
27926
+ logger.log('destroy');
28919
27927
  this.trigger(Events.DESTROYING, undefined);
28920
27928
  this.detachMedia();
28921
27929
  this.removeAllListeners();
@@ -28936,7 +27944,7 @@ class Hls {
28936
27944
  * Attaches Hls.js to a media element
28937
27945
  */
28938
27946
  attachMedia(media) {
28939
- this.logger.log('attachMedia');
27947
+ logger.log('attachMedia');
28940
27948
  this._media = media;
28941
27949
  this.trigger(Events.MEDIA_ATTACHING, {
28942
27950
  media: media
@@ -28947,7 +27955,7 @@ class Hls {
28947
27955
  * Detach Hls.js from the media
28948
27956
  */
28949
27957
  detachMedia() {
28950
- this.logger.log('detachMedia');
27958
+ logger.log('detachMedia');
28951
27959
  this.trigger(Events.MEDIA_DETACHING, undefined);
28952
27960
  this._media = null;
28953
27961
  }
@@ -28964,7 +27972,7 @@ class Hls {
28964
27972
  });
28965
27973
  this._autoLevelCapping = -1;
28966
27974
  this._maxHdcpLevel = null;
28967
- this.logger.log(`loadSource:${loadingSource}`);
27975
+ logger.log(`loadSource:${loadingSource}`);
28968
27976
  if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
28969
27977
  this.detachMedia();
28970
27978
  this.attachMedia(media);
@@ -28983,7 +27991,8 @@ class Hls {
28983
27991
  * Defaults to -1 (None: starts from earliest point)
28984
27992
  */
28985
27993
  startLoad(startPosition = -1) {
28986
- this.logger.log(`startLoad(${startPosition})`);
27994
+ logger.log(`startLoad(${startPosition})`);
27995
+ this.started = true;
28987
27996
  this.networkControllers.forEach(controller => {
28988
27997
  controller.startLoad(startPosition);
28989
27998
  });
@@ -28993,31 +28002,34 @@ class Hls {
28993
28002
  * Stop loading of any stream data.
28994
28003
  */
28995
28004
  stopLoad() {
28996
- this.logger.log('stopLoad');
28005
+ logger.log('stopLoad');
28006
+ this.started = false;
28997
28007
  this.networkControllers.forEach(controller => {
28998
28008
  controller.stopLoad();
28999
28009
  });
29000
28010
  }
29001
28011
 
29002
28012
  /**
29003
- * Resumes stream controller segment loading after `pauseBuffering` has been called.
28013
+ * Resumes stream controller segment loading if previously started.
29004
28014
  */
29005
28015
  resumeBuffering() {
29006
- this.networkControllers.forEach(controller => {
29007
- if (controller.resumeBuffering) {
29008
- controller.resumeBuffering();
29009
- }
29010
- });
28016
+ if (this.started) {
28017
+ this.networkControllers.forEach(controller => {
28018
+ if ('fragmentLoader' in controller) {
28019
+ controller.startLoad(-1);
28020
+ }
28021
+ });
28022
+ }
29011
28023
  }
29012
28024
 
29013
28025
  /**
29014
- * Prevents stream controller from loading new segments until `resumeBuffering` is called.
28026
+ * Stops stream controller segment loading without changing 'started' state like stopLoad().
29015
28027
  * This allows for media buffering to be paused without interupting playlist loading.
29016
28028
  */
29017
28029
  pauseBuffering() {
29018
28030
  this.networkControllers.forEach(controller => {
29019
- if (controller.pauseBuffering) {
29020
- controller.pauseBuffering();
28031
+ if ('fragmentLoader' in controller) {
28032
+ controller.stopLoad();
29021
28033
  }
29022
28034
  });
29023
28035
  }
@@ -29026,7 +28038,7 @@ class Hls {
29026
28038
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
29027
28039
  */
29028
28040
  swapAudioCodec() {
29029
- this.logger.log('swapAudioCodec');
28041
+ logger.log('swapAudioCodec');
29030
28042
  this.streamController.swapAudioCodec();
29031
28043
  }
29032
28044
 
@@ -29037,7 +28049,7 @@ class Hls {
29037
28049
  * Automatic recovery of media-errors by this process is configurable.
29038
28050
  */
29039
28051
  recoverMediaError() {
29040
- this.logger.log('recoverMediaError');
28052
+ logger.log('recoverMediaError');
29041
28053
  const media = this._media;
29042
28054
  this.detachMedia();
29043
28055
  if (media) {
@@ -29067,7 +28079,7 @@ class Hls {
29067
28079
  * 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.
29068
28080
  */
29069
28081
  set currentLevel(newLevel) {
29070
- this.logger.log(`set currentLevel:${newLevel}`);
28082
+ logger.log(`set currentLevel:${newLevel}`);
29071
28083
  this.levelController.manualLevel = newLevel;
29072
28084
  this.streamController.immediateLevelSwitch();
29073
28085
  }
@@ -29086,7 +28098,7 @@ class Hls {
29086
28098
  * @param newLevel - Pass -1 for automatic level selection
29087
28099
  */
29088
28100
  set nextLevel(newLevel) {
29089
- this.logger.log(`set nextLevel:${newLevel}`);
28101
+ logger.log(`set nextLevel:${newLevel}`);
29090
28102
  this.levelController.manualLevel = newLevel;
29091
28103
  this.streamController.nextLevelSwitch();
29092
28104
  }
@@ -29105,7 +28117,7 @@ class Hls {
29105
28117
  * @param newLevel - Pass -1 for automatic level selection
29106
28118
  */
29107
28119
  set loadLevel(newLevel) {
29108
- this.logger.log(`set loadLevel:${newLevel}`);
28120
+ logger.log(`set loadLevel:${newLevel}`);
29109
28121
  this.levelController.manualLevel = newLevel;
29110
28122
  }
29111
28123
 
@@ -29136,7 +28148,7 @@ class Hls {
29136
28148
  * Sets "first-level", see getter.
29137
28149
  */
29138
28150
  set firstLevel(newLevel) {
29139
- this.logger.log(`set firstLevel:${newLevel}`);
28151
+ logger.log(`set firstLevel:${newLevel}`);
29140
28152
  this.levelController.firstLevel = newLevel;
29141
28153
  }
29142
28154
 
@@ -29161,7 +28173,7 @@ class Hls {
29161
28173
  * (determined from download of first segment)
29162
28174
  */
29163
28175
  set startLevel(newLevel) {
29164
- this.logger.log(`set startLevel:${newLevel}`);
28176
+ logger.log(`set startLevel:${newLevel}`);
29165
28177
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
29166
28178
  if (newLevel !== -1) {
29167
28179
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -29236,7 +28248,7 @@ class Hls {
29236
28248
  */
29237
28249
  set autoLevelCapping(newLevel) {
29238
28250
  if (this._autoLevelCapping !== newLevel) {
29239
- this.logger.log(`set autoLevelCapping:${newLevel}`);
28251
+ logger.log(`set autoLevelCapping:${newLevel}`);
29240
28252
  this._autoLevelCapping = newLevel;
29241
28253
  this.levelController.checkMaxAutoUpdated();
29242
28254
  }
@@ -29341,9 +28353,6 @@ class Hls {
29341
28353
  get mainForwardBufferInfo() {
29342
28354
  return this.streamController.getMainFwdBufferInfo();
29343
28355
  }
29344
- get maxBufferLength() {
29345
- return this.streamController.maxBufferLength;
29346
- }
29347
28356
 
29348
28357
  /**
29349
28358
  * Find and select the best matching audio track, making a level switch when a Group change is necessary.