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

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