hls.js 1.5.5 → 1.5.6-0.canary.10003

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 +1 -0
  2. package/dist/hls-demo.js +10 -0
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +2075 -1166
  5. package/dist/hls.js.d.ts +65 -50
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1148 -859
  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 +984 -696
  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 +1757 -863
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +20 -20
  20. package/src/config.ts +3 -2
  21. package/src/controller/abr-controller.ts +21 -20
  22. package/src/controller/audio-stream-controller.ts +15 -16
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +20 -8
  25. package/src/controller/base-stream-controller.ts +149 -33
  26. package/src/controller/buffer-controller.ts +11 -11
  27. package/src/controller/cap-level-controller.ts +1 -2
  28. package/src/controller/cmcd-controller.ts +27 -6
  29. package/src/controller/content-steering-controller.ts +8 -6
  30. package/src/controller/eme-controller.ts +9 -22
  31. package/src/controller/error-controller.ts +6 -8
  32. package/src/controller/fps-controller.ts +2 -3
  33. package/src/controller/gap-controller.ts +43 -16
  34. package/src/controller/latency-controller.ts +9 -11
  35. package/src/controller/level-controller.ts +12 -18
  36. package/src/controller/stream-controller.ts +25 -32
  37. package/src/controller/subtitle-stream-controller.ts +13 -14
  38. package/src/controller/subtitle-track-controller.ts +5 -3
  39. package/src/controller/timeline-controller.ts +23 -30
  40. package/src/crypt/aes-crypto.ts +21 -2
  41. package/src/crypt/decrypter-aes-mode.ts +4 -0
  42. package/src/crypt/decrypter.ts +32 -18
  43. package/src/crypt/fast-aes-key.ts +24 -5
  44. package/src/demux/audio/adts.ts +9 -4
  45. package/src/demux/sample-aes.ts +2 -0
  46. package/src/demux/transmuxer-interface.ts +4 -12
  47. package/src/demux/transmuxer-worker.ts +4 -4
  48. package/src/demux/transmuxer.ts +16 -3
  49. package/src/demux/tsdemuxer.ts +71 -37
  50. package/src/demux/video/avc-video-parser.ts +208 -119
  51. package/src/demux/video/base-video-parser.ts +134 -2
  52. package/src/demux/video/exp-golomb.ts +0 -208
  53. package/src/demux/video/hevc-video-parser.ts +746 -0
  54. package/src/events.ts +7 -0
  55. package/src/hls.ts +42 -34
  56. package/src/loader/fragment-loader.ts +9 -2
  57. package/src/loader/key-loader.ts +2 -0
  58. package/src/loader/level-key.ts +10 -9
  59. package/src/loader/playlist-loader.ts +4 -5
  60. package/src/remux/mp4-generator.ts +196 -1
  61. package/src/remux/mp4-remuxer.ts +23 -7
  62. package/src/task-loop.ts +5 -2
  63. package/src/types/component-api.ts +2 -0
  64. package/src/types/demuxer.ts +3 -0
  65. package/src/types/events.ts +4 -0
  66. package/src/utils/codecs.ts +33 -4
  67. package/src/utils/encryption-methods-util.ts +21 -0
  68. package/src/utils/logger.ts +54 -24
package/dist/hls.mjs CHANGED
@@ -256,6 +256,7 @@ let Events = /*#__PURE__*/function (Events) {
256
256
  Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
257
257
  Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
258
258
  Events["MEDIA_DETACHED"] = "hlsMediaDetached";
259
+ Events["MEDIA_ENDED"] = "hlsMediaEnded";
259
260
  Events["BUFFER_RESET"] = "hlsBufferReset";
260
261
  Events["BUFFER_CODECS"] = "hlsBufferCodecs";
261
262
  Events["BUFFER_CREATED"] = "hlsBufferCreated";
@@ -369,58 +370,6 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
369
370
  return ErrorDetails;
370
371
  }({});
371
372
 
372
- const noop = function noop() {};
373
- const fakeLogger = {
374
- trace: noop,
375
- debug: noop,
376
- log: noop,
377
- warn: noop,
378
- info: noop,
379
- error: noop
380
- };
381
- let exportedLogger = fakeLogger;
382
-
383
- // let lastCallTime;
384
- // function formatMsgWithTimeInfo(type, msg) {
385
- // const now = Date.now();
386
- // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
387
- // lastCallTime = now;
388
- // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
389
- // return msg;
390
- // }
391
-
392
- function consolePrintFn(type) {
393
- const func = self.console[type];
394
- if (func) {
395
- return func.bind(self.console, `[${type}] >`);
396
- }
397
- return noop;
398
- }
399
- function exportLoggerFunctions(debugConfig, ...functions) {
400
- functions.forEach(function (type) {
401
- exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
402
- });
403
- }
404
- function enableLogs(debugConfig, id) {
405
- // check that console is available
406
- if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
407
- exportLoggerFunctions(debugConfig,
408
- // Remove out from list here to hard-disable a log-level
409
- // 'trace',
410
- 'debug', 'log', 'info', 'warn', 'error');
411
- // Some browsers don't allow to use bind on console object anyway
412
- // fallback to default if needed
413
- try {
414
- exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.5"}`);
415
- } catch (e) {
416
- exportedLogger = fakeLogger;
417
- }
418
- } else {
419
- exportedLogger = fakeLogger;
420
- }
421
- }
422
- const logger = exportedLogger;
423
-
424
373
  const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
425
374
  const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
426
375
 
@@ -502,6 +451,79 @@ class AttrList {
502
451
  }
503
452
  }
504
453
 
454
+ class Logger {
455
+ constructor(label, logger) {
456
+ this.trace = void 0;
457
+ this.debug = void 0;
458
+ this.log = void 0;
459
+ this.warn = void 0;
460
+ this.info = void 0;
461
+ this.error = void 0;
462
+ const lb = `[${label}]:`;
463
+ this.trace = noop;
464
+ this.debug = logger.debug.bind(null, lb);
465
+ this.log = logger.log.bind(null, lb);
466
+ this.warn = logger.warn.bind(null, lb);
467
+ this.info = logger.info.bind(null, lb);
468
+ this.error = logger.error.bind(null, lb);
469
+ }
470
+ }
471
+ const noop = function noop() {};
472
+ const fakeLogger = {
473
+ trace: noop,
474
+ debug: noop,
475
+ log: noop,
476
+ warn: noop,
477
+ info: noop,
478
+ error: noop
479
+ };
480
+ function createLogger() {
481
+ return _extends({}, fakeLogger);
482
+ }
483
+
484
+ // let lastCallTime;
485
+ // function formatMsgWithTimeInfo(type, msg) {
486
+ // const now = Date.now();
487
+ // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
488
+ // lastCallTime = now;
489
+ // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
490
+ // return msg;
491
+ // }
492
+
493
+ function consolePrintFn(type, id) {
494
+ const func = self.console[type];
495
+ return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
496
+ }
497
+ function getLoggerFn(key, debugConfig, id) {
498
+ return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
499
+ }
500
+ const exportedLogger = createLogger();
501
+ function enableLogs(debugConfig, context, id) {
502
+ // check that console is available
503
+ const newLogger = createLogger();
504
+ if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
505
+ const keys = [
506
+ // Remove out from list here to hard-disable a log-level
507
+ // 'trace',
508
+ 'debug', 'log', 'info', 'warn', 'error'];
509
+ keys.forEach(key => {
510
+ newLogger[key] = getLoggerFn(key, debugConfig, id);
511
+ });
512
+ // Some browsers don't allow to use bind on console object anyway
513
+ // fallback to default if needed
514
+ try {
515
+ newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.6-0.canary.10003"}`);
516
+ } catch (e) {
517
+ /* log fn threw an exception. All logger methods are no-ops. */
518
+ return createLogger();
519
+ }
520
+ }
521
+ // global exported logger uses the log methods from last call to `enableLogs`
522
+ _extends(exportedLogger, newLogger);
523
+ return newLogger;
524
+ }
525
+ const logger = exportedLogger;
526
+
505
527
  // Avoid exporting const enum so that these values can be inlined
506
528
 
507
529
  function isDateRangeCueAttribute(attrName) {
@@ -1036,6 +1058,26 @@ function strToUtf8array(str) {
1036
1058
  return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
1037
1059
  }
1038
1060
 
1061
+ var DecrypterAesMode = {
1062
+ cbc: 0,
1063
+ ctr: 1
1064
+ };
1065
+
1066
+ function isFullSegmentEncryption(method) {
1067
+ return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
1068
+ }
1069
+ function getAesModeFromFullSegmentMethod(method) {
1070
+ switch (method) {
1071
+ case 'AES-128':
1072
+ case 'AES-256':
1073
+ return DecrypterAesMode.cbc;
1074
+ case 'AES-256-CTR':
1075
+ return DecrypterAesMode.ctr;
1076
+ default:
1077
+ throw new Error(`invalid full segment method ${method}`);
1078
+ }
1079
+ }
1080
+
1039
1081
  /** returns `undefined` is `self` is missing, e.g. in node */
1040
1082
  const optionalSelf = typeof self !== 'undefined' ? self : undefined;
1041
1083
 
@@ -2686,12 +2728,12 @@ class LevelKey {
2686
2728
  this.keyFormatVersions = formatversions;
2687
2729
  this.iv = iv;
2688
2730
  this.encrypted = method ? method !== 'NONE' : false;
2689
- this.isCommonEncryption = this.encrypted && method !== 'AES-128';
2731
+ this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
2690
2732
  }
2691
2733
  isSupported() {
2692
2734
  // If it's Segment encryption or No encryption, just select that key system
2693
2735
  if (this.method) {
2694
- if (this.method === 'AES-128' || this.method === 'NONE') {
2736
+ if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
2695
2737
  return true;
2696
2738
  }
2697
2739
  if (this.keyFormat === 'identity') {
@@ -2713,14 +2755,13 @@ class LevelKey {
2713
2755
  if (!this.encrypted || !this.uri) {
2714
2756
  return null;
2715
2757
  }
2716
- if (this.method === 'AES-128' && this.uri && !this.iv) {
2758
+ if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
2717
2759
  if (typeof sn !== 'number') {
2718
2760
  // We are fetching decryption data for a initialization segment
2719
- // If the segment was encrypted with AES-128
2761
+ // If the segment was encrypted with AES-128/256
2720
2762
  // It must have an IV defined. We cannot substitute the Segment Number in.
2721
- if (this.method === 'AES-128' && !this.iv) {
2722
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2723
- }
2763
+ logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2764
+
2724
2765
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
2725
2766
  sn = 0;
2726
2767
  }
@@ -2999,23 +3040,28 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
2999
3040
  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
3000
3041
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
3001
3042
  }
3002
-
3003
- // Idealy fLaC and Opus would be first (spec-compliant) but
3004
- // some browsers will report that fLaC is supported then fail.
3005
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3006
3043
  const codecsToCheck = {
3044
+ // Idealy fLaC and Opus would be first (spec-compliant) but
3045
+ // some browsers will report that fLaC is supported then fail.
3046
+ // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3007
3047
  flac: ['flac', 'fLaC', 'FLAC'],
3008
- opus: ['opus', 'Opus']
3048
+ opus: ['opus', 'Opus'],
3049
+ // Replace audio codec info if browser does not support mp4a.40.34,
3050
+ // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
3051
+ 'mp4a.40.34': ['mp3']
3009
3052
  }[lowerCaseCodec];
3010
3053
  for (let i = 0; i < codecsToCheck.length; i++) {
3054
+ var _getMediaSource;
3011
3055
  if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
3012
3056
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
3013
3057
  return codecsToCheck[i];
3058
+ } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
3059
+ return '';
3014
3060
  }
3015
3061
  }
3016
3062
  return lowerCaseCodec;
3017
3063
  }
3018
- const AUDIO_CODEC_REGEXP = /flac|opus/i;
3064
+ const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
3019
3065
  function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
3020
3066
  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
3021
3067
  }
@@ -3038,6 +3084,16 @@ function convertAVC1ToAVCOTI(codec) {
3038
3084
  }
3039
3085
  return codec;
3040
3086
  }
3087
+ function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
3088
+ const MediaSource = getMediaSource(preferManagedMediaSource) || {
3089
+ isTypeSupported: () => false
3090
+ };
3091
+ return {
3092
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
3093
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
3094
+ ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
3095
+ };
3096
+ }
3041
3097
 
3042
3098
  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;
3043
3099
  const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -3887,10 +3943,10 @@ class PlaylistLoader {
3887
3943
  const loaderContext = loader.context;
3888
3944
  if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
3889
3945
  // same URL can't overlap
3890
- logger.trace('[playlist-loader]: playlist request ongoing');
3946
+ this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
3891
3947
  return;
3892
3948
  }
3893
- logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3949
+ this.hls.logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3894
3950
  loader.abort();
3895
3951
  }
3896
3952
 
@@ -4000,7 +4056,7 @@ class PlaylistLoader {
4000
4056
  // alt audio rendition in which quality levels (main)
4001
4057
  // contains both audio+video. but with mixed audio track not signaled
4002
4058
  if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
4003
- logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
4059
+ this.hls.logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
4004
4060
  audioTracks.unshift({
4005
4061
  type: 'main',
4006
4062
  name: 'main',
@@ -4099,7 +4155,7 @@ class PlaylistLoader {
4099
4155
  message += ` id: ${context.id} group-id: "${context.groupId}"`;
4100
4156
  }
4101
4157
  const error = new Error(message);
4102
- logger.warn(`[playlist-loader]: ${message}`);
4158
+ this.hls.logger.warn(`[playlist-loader]: ${message}`);
4103
4159
  let details = ErrorDetails.UNKNOWN;
4104
4160
  let fatal = false;
4105
4161
  const loader = this.getInternalLoader(context);
@@ -4704,7 +4760,47 @@ class LatencyController {
4704
4760
  this.currentTime = 0;
4705
4761
  this.stallCount = 0;
4706
4762
  this._latency = null;
4707
- this.timeupdateHandler = () => this.timeupdate();
4763
+ this.onTimeupdate = () => {
4764
+ const {
4765
+ media,
4766
+ levelDetails
4767
+ } = this;
4768
+ if (!media || !levelDetails) {
4769
+ return;
4770
+ }
4771
+ this.currentTime = media.currentTime;
4772
+ const latency = this.computeLatency();
4773
+ if (latency === null) {
4774
+ return;
4775
+ }
4776
+ this._latency = latency;
4777
+
4778
+ // Adapt playbackRate to meet target latency in low-latency mode
4779
+ const {
4780
+ lowLatencyMode,
4781
+ maxLiveSyncPlaybackRate
4782
+ } = this.config;
4783
+ if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4784
+ return;
4785
+ }
4786
+ const targetLatency = this.targetLatency;
4787
+ if (targetLatency === null) {
4788
+ return;
4789
+ }
4790
+ const distanceFromTarget = latency - targetLatency;
4791
+ // Only adjust playbackRate when within one target duration of targetLatency
4792
+ // and more than one second from under-buffering.
4793
+ // Playback further than one target duration from target can be considered DVR playback.
4794
+ const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4795
+ const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4796
+ if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4797
+ const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4798
+ const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4799
+ media.playbackRate = Math.min(max, Math.max(1, rate));
4800
+ } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4801
+ media.playbackRate = 1;
4802
+ }
4803
+ };
4708
4804
  this.hls = hls;
4709
4805
  this.config = hls.config;
4710
4806
  this.registerListeners();
@@ -4796,7 +4892,7 @@ class LatencyController {
4796
4892
  this.onMediaDetaching();
4797
4893
  this.levelDetails = null;
4798
4894
  // @ts-ignore
4799
- this.hls = this.timeupdateHandler = null;
4895
+ this.hls = null;
4800
4896
  }
4801
4897
  registerListeners() {
4802
4898
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -4814,11 +4910,11 @@ class LatencyController {
4814
4910
  }
4815
4911
  onMediaAttached(event, data) {
4816
4912
  this.media = data.media;
4817
- this.media.addEventListener('timeupdate', this.timeupdateHandler);
4913
+ this.media.addEventListener('timeupdate', this.onTimeupdate);
4818
4914
  }
4819
4915
  onMediaDetaching() {
4820
4916
  if (this.media) {
4821
- this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4917
+ this.media.removeEventListener('timeupdate', this.onTimeupdate);
4822
4918
  this.media = null;
4823
4919
  }
4824
4920
  }
@@ -4832,10 +4928,10 @@ class LatencyController {
4832
4928
  }) {
4833
4929
  this.levelDetails = details;
4834
4930
  if (details.advanced) {
4835
- this.timeupdate();
4931
+ this.onTimeupdate();
4836
4932
  }
4837
4933
  if (!details.live && this.media) {
4838
- this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4934
+ this.media.removeEventListener('timeupdate', this.onTimeupdate);
4839
4935
  }
4840
4936
  }
4841
4937
  onError(event, data) {
@@ -4845,48 +4941,7 @@ class LatencyController {
4845
4941
  }
4846
4942
  this.stallCount++;
4847
4943
  if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
4848
- logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
4849
- }
4850
- }
4851
- timeupdate() {
4852
- const {
4853
- media,
4854
- levelDetails
4855
- } = this;
4856
- if (!media || !levelDetails) {
4857
- return;
4858
- }
4859
- this.currentTime = media.currentTime;
4860
- const latency = this.computeLatency();
4861
- if (latency === null) {
4862
- return;
4863
- }
4864
- this._latency = latency;
4865
-
4866
- // Adapt playbackRate to meet target latency in low-latency mode
4867
- const {
4868
- lowLatencyMode,
4869
- maxLiveSyncPlaybackRate
4870
- } = this.config;
4871
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4872
- return;
4873
- }
4874
- const targetLatency = this.targetLatency;
4875
- if (targetLatency === null) {
4876
- return;
4877
- }
4878
- const distanceFromTarget = latency - targetLatency;
4879
- // Only adjust playbackRate when within one target duration of targetLatency
4880
- // and more than one second from under-buffering.
4881
- // Playback further than one target duration from target can be considered DVR playback.
4882
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4883
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4884
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4885
- const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4886
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4887
- media.playbackRate = Math.min(max, Math.max(1, rate));
4888
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4889
- media.playbackRate = 1;
4944
+ this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
4890
4945
  }
4891
4946
  }
4892
4947
  estimateLiveEdge() {
@@ -5658,18 +5713,13 @@ var ErrorActionFlags = {
5658
5713
  MoveAllAlternatesMatchingHDCP: 2,
5659
5714
  SwitchToSDR: 4
5660
5715
  }; // Reserved for future use
5661
- class ErrorController {
5716
+ class ErrorController extends Logger {
5662
5717
  constructor(hls) {
5718
+ super('error-controller', hls.logger);
5663
5719
  this.hls = void 0;
5664
5720
  this.playlistError = 0;
5665
5721
  this.penalizedRenditions = {};
5666
- this.log = void 0;
5667
- this.warn = void 0;
5668
- this.error = void 0;
5669
5722
  this.hls = hls;
5670
- this.log = logger.log.bind(logger, `[info]:`);
5671
- this.warn = logger.warn.bind(logger, `[warning]:`);
5672
- this.error = logger.error.bind(logger, `[error]:`);
5673
5723
  this.registerListeners();
5674
5724
  }
5675
5725
  registerListeners() {
@@ -6021,16 +6071,13 @@ class ErrorController {
6021
6071
  }
6022
6072
  }
6023
6073
 
6024
- class BasePlaylistController {
6074
+ class BasePlaylistController extends Logger {
6025
6075
  constructor(hls, logPrefix) {
6076
+ super(logPrefix, hls.logger);
6026
6077
  this.hls = void 0;
6027
6078
  this.timer = -1;
6028
6079
  this.requestScheduled = -1;
6029
6080
  this.canLoad = false;
6030
- this.log = void 0;
6031
- this.warn = void 0;
6032
- this.log = logger.log.bind(logger, `${logPrefix}:`);
6033
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
6034
6081
  this.hls = hls;
6035
6082
  }
6036
6083
  destroy() {
@@ -6063,7 +6110,7 @@ class BasePlaylistController {
6063
6110
  try {
6064
6111
  uri = new self.URL(attr.URI, previous.url).href;
6065
6112
  } catch (error) {
6066
- logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
6113
+ this.warn(`Could not construct new URL for Rendition Report: ${error}`);
6067
6114
  uri = attr.URI || '';
6068
6115
  }
6069
6116
  // Use exact match. Otherwise, the last partial match, if any, will be used
@@ -6150,7 +6197,12 @@ class BasePlaylistController {
6150
6197
  const cdnAge = lastAdvanced + details.ageHeader;
6151
6198
  let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
6152
6199
  if (currentGoal > 0) {
6153
- if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
6200
+ if (cdnAge > details.targetduration * 3) {
6201
+ // Omit segment and part directives when the last response was more than 3 target durations ago,
6202
+ this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
6203
+ msn = undefined;
6204
+ part = undefined;
6205
+ } else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
6154
6206
  // If we attempted to get the next or latest playlist update, but currentGoal increased,
6155
6207
  // then we either can't catchup, or the "age" header cannot be trusted.
6156
6208
  this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
@@ -6822,8 +6874,9 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
6822
6874
  return -1;
6823
6875
  }
6824
6876
 
6825
- class AbrController {
6877
+ class AbrController extends Logger {
6826
6878
  constructor(_hls) {
6879
+ super('abr', _hls.logger);
6827
6880
  this.hls = void 0;
6828
6881
  this.lastLevelLoadSec = 0;
6829
6882
  this.lastLoadedFragLevel = -1;
@@ -6937,7 +6990,7 @@ class AbrController {
6937
6990
  this.resetEstimator(nextLoadLevelBitrate);
6938
6991
  }
6939
6992
  this.clearTimer();
6940
- logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6993
+ this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6941
6994
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
6942
6995
  Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
6943
6996
  Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
@@ -6957,7 +7010,7 @@ class AbrController {
6957
7010
  }
6958
7011
  resetEstimator(abrEwmaDefaultEstimate) {
6959
7012
  if (abrEwmaDefaultEstimate) {
6960
- logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
7013
+ this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6961
7014
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
6962
7015
  }
6963
7016
  this.firstSelection = -1;
@@ -7189,7 +7242,7 @@ class AbrController {
7189
7242
  }
7190
7243
  const firstLevel = this.hls.firstLevel;
7191
7244
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
7192
- logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7245
+ this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7193
7246
  return clamped;
7194
7247
  }
7195
7248
  get forcedAutoLevel() {
@@ -7274,13 +7327,13 @@ class AbrController {
7274
7327
  // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
7275
7328
  const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
7276
7329
  maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
7277
- logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7330
+ this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7278
7331
  // don't use conservative factor on bitrate test
7279
7332
  bwFactor = bwUpFactor = 1;
7280
7333
  }
7281
7334
  }
7282
7335
  const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
7283
- logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7336
+ this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7284
7337
  if (bestLevel > -1) {
7285
7338
  return bestLevel;
7286
7339
  }
@@ -7342,7 +7395,7 @@ class AbrController {
7342
7395
  currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
7343
7396
  currentFrameRate = minFramerate;
7344
7397
  currentBw = Math.max(currentBw, minBitrate);
7345
- logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
7398
+ this.log(`picked start tier ${JSON.stringify(startTier)}`);
7346
7399
  } else {
7347
7400
  currentCodecSet = level == null ? void 0 : level.codecSet;
7348
7401
  currentVideoRange = level == null ? void 0 : level.videoRange;
@@ -7366,11 +7419,11 @@ class AbrController {
7366
7419
  const levels = this.hls.levels;
7367
7420
  const index = levels.indexOf(levelInfo);
7368
7421
  if (decodingInfo.error) {
7369
- logger.warn(`[abr] MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7422
+ this.warn(`MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7370
7423
  } else if (!decodingInfo.supported) {
7371
- logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7424
+ this.warn(`Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7372
7425
  if (index > -1 && levels.length > 1) {
7373
- logger.log(`[abr] Removing unsupported level ${index}`);
7426
+ this.log(`Removing unsupported level ${index}`);
7374
7427
  this.hls.removeLevel(index);
7375
7428
  }
7376
7429
  }
@@ -7417,9 +7470,9 @@ class AbrController {
7417
7470
  const forcedAutoLevel = this.forcedAutoLevel;
7418
7471
  if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
7419
7472
  if (levelsSkipped.length) {
7420
- 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}`);
7473
+ this.trace(`Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
7421
7474
  }
7422
- 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}`);
7475
+ this.info(`switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
7423
7476
  }
7424
7477
  if (firstSelection) {
7425
7478
  this.firstSelection = i;
@@ -7473,8 +7526,9 @@ class AbrController {
7473
7526
  * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
7474
7527
  * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
7475
7528
  */
7476
- class TaskLoop {
7477
- constructor() {
7529
+ class TaskLoop extends Logger {
7530
+ constructor(label, logger) {
7531
+ super(label, logger);
7478
7532
  this._boundTick = void 0;
7479
7533
  this._tickTimer = null;
7480
7534
  this._tickInterval = null;
@@ -8565,8 +8619,8 @@ function createLoaderContext(frag, part = null) {
8565
8619
  var _frag$decryptdata;
8566
8620
  let byteRangeStart = start;
8567
8621
  let byteRangeEnd = end;
8568
- if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
8569
- // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
8622
+ if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
8623
+ // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
8570
8624
  // has the unencrypted size specified in the range.
8571
8625
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
8572
8626
  const fragmentLen = end - start;
@@ -8599,6 +8653,9 @@ function createGapLoadError(frag, part) {
8599
8653
  (part ? part : frag).stats.aborted = true;
8600
8654
  return new LoadError(errorData);
8601
8655
  }
8656
+ function isMethodFullSegmentAesCbc(method) {
8657
+ return method === 'AES-128' || method === 'AES-256';
8658
+ }
8602
8659
  class LoadError extends Error {
8603
8660
  constructor(data) {
8604
8661
  super(data.error.message);
@@ -8608,33 +8665,61 @@ class LoadError extends Error {
8608
8665
  }
8609
8666
 
8610
8667
  class AESCrypto {
8611
- constructor(subtle, iv) {
8668
+ constructor(subtle, iv, aesMode) {
8612
8669
  this.subtle = void 0;
8613
8670
  this.aesIV = void 0;
8671
+ this.aesMode = void 0;
8614
8672
  this.subtle = subtle;
8615
8673
  this.aesIV = iv;
8674
+ this.aesMode = aesMode;
8616
8675
  }
8617
8676
  decrypt(data, key) {
8618
- return this.subtle.decrypt({
8619
- name: 'AES-CBC',
8620
- iv: this.aesIV
8621
- }, key, data);
8677
+ switch (this.aesMode) {
8678
+ case DecrypterAesMode.cbc:
8679
+ return this.subtle.decrypt({
8680
+ name: 'AES-CBC',
8681
+ iv: this.aesIV
8682
+ }, key, data);
8683
+ case DecrypterAesMode.ctr:
8684
+ return this.subtle.decrypt({
8685
+ name: 'AES-CTR',
8686
+ counter: this.aesIV,
8687
+ length: 64
8688
+ },
8689
+ //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
8690
+ key, data);
8691
+ default:
8692
+ throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
8693
+ }
8622
8694
  }
8623
8695
  }
8624
8696
 
8625
8697
  class FastAESKey {
8626
- constructor(subtle, key) {
8698
+ constructor(subtle, key, aesMode) {
8627
8699
  this.subtle = void 0;
8628
8700
  this.key = void 0;
8701
+ this.aesMode = void 0;
8629
8702
  this.subtle = subtle;
8630
8703
  this.key = key;
8704
+ this.aesMode = aesMode;
8631
8705
  }
8632
8706
  expandKey() {
8707
+ const subtleAlgoName = getSubtleAlgoName(this.aesMode);
8633
8708
  return this.subtle.importKey('raw', this.key, {
8634
- name: 'AES-CBC'
8709
+ name: subtleAlgoName
8635
8710
  }, false, ['encrypt', 'decrypt']);
8636
8711
  }
8637
8712
  }
8713
+ function getSubtleAlgoName(aesMode) {
8714
+ switch (aesMode) {
8715
+ case DecrypterAesMode.cbc:
8716
+ return 'AES-CBC';
8717
+ case DecrypterAesMode.ctr:
8718
+ return 'AES-CTR';
8719
+ default:
8720
+ throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
8721
+ }
8722
+ }
8638
8723
 
8639
8724
  // PKCS7
8640
8725
  function removePadding(array) {
@@ -8884,7 +8969,8 @@ class Decrypter {
8884
8969
  this.currentIV = null;
8885
8970
  this.currentResult = null;
8886
8971
  this.useSoftware = void 0;
8887
- this.useSoftware = config.enableSoftwareAES;
8972
+ this.enableSoftwareAES = void 0;
8973
+ this.enableSoftwareAES = config.enableSoftwareAES;
8888
8974
  this.removePKCS7Padding = removePKCS7Padding;
8889
8975
  // built in decryptor expects PKCS7 padding
8890
8976
  if (removePKCS7Padding) {
@@ -8897,9 +8983,7 @@ class Decrypter {
8897
8983
  /* no-op */
8898
8984
  }
8899
8985
  }
8900
- if (this.subtle === null) {
8901
- this.useSoftware = true;
8902
- }
8986
+ this.useSoftware = this.subtle === null;
8903
8987
  }
8904
8988
  destroy() {
8905
8989
  this.subtle = null;
@@ -8937,10 +9021,10 @@ class Decrypter {
8937
9021
  this.softwareDecrypter = null;
8938
9022
  }
8939
9023
  }
8940
- decrypt(data, key, iv) {
9024
+ decrypt(data, key, iv, aesMode) {
8941
9025
  if (this.useSoftware) {
8942
9026
  return new Promise((resolve, reject) => {
8943
- this.softwareDecrypt(new Uint8Array(data), key, iv);
9027
+ this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
8944
9028
  const decryptResult = this.flush();
8945
9029
  if (decryptResult) {
8946
9030
  resolve(decryptResult.buffer);
@@ -8949,17 +9033,21 @@ class Decrypter {
8949
9033
  }
8950
9034
  });
8951
9035
  }
8952
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
9036
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
8953
9037
  }
8954
9038
 
8955
9039
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
8956
9040
  // data is handled in the flush() call
8957
- softwareDecrypt(data, key, iv) {
9041
+ softwareDecrypt(data, key, iv, aesMode) {
8958
9042
  const {
8959
9043
  currentIV,
8960
9044
  currentResult,
8961
9045
  remainderData
8962
9046
  } = this;
9047
+ if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
9048
+ logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
9049
+ return null;
9050
+ }
8963
9051
  this.logOnce('JS AES decrypt');
8964
9052
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
8965
9053
  // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
@@ -8992,11 +9080,11 @@ class Decrypter {
8992
9080
  }
8993
9081
  return result;
8994
9082
  }
8995
- webCryptoDecrypt(data, key, iv) {
9083
+ webCryptoDecrypt(data, key, iv, aesMode) {
8996
9084
  const subtle = this.subtle;
8997
9085
  if (this.key !== key || !this.fastAesKey) {
8998
9086
  this.key = key;
8999
- this.fastAesKey = new FastAESKey(subtle, key);
9087
+ this.fastAesKey = new FastAESKey(subtle, key, aesMode);
9000
9088
  }
9001
9089
  return this.fastAesKey.expandKey().then(aesKey => {
9002
9090
  // decrypt using web crypto
@@ -9004,22 +9092,25 @@ class Decrypter {
9004
9092
  return Promise.reject(new Error('web crypto not initialized'));
9005
9093
  }
9006
9094
  this.logOnce('WebCrypto AES decrypt');
9007
- const crypto = new AESCrypto(subtle, new Uint8Array(iv));
9095
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
9008
9096
  return crypto.decrypt(data.buffer, aesKey);
9009
9097
  }).catch(err => {
9010
9098
  logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
9011
- return this.onWebCryptoError(data, key, iv);
9099
+ return this.onWebCryptoError(data, key, iv, aesMode);
9012
9100
  });
9013
9101
  }
9014
- onWebCryptoError(data, key, iv) {
9015
- this.useSoftware = true;
9016
- this.logEnabled = true;
9017
- this.softwareDecrypt(data, key, iv);
9018
- const decryptResult = this.flush();
9019
- if (decryptResult) {
9020
- return decryptResult.buffer;
9102
+ onWebCryptoError(data, key, iv, aesMode) {
9103
+ const enableSoftwareAES = this.enableSoftwareAES;
9104
+ if (enableSoftwareAES) {
9105
+ this.useSoftware = true;
9106
+ this.logEnabled = true;
9107
+ this.softwareDecrypt(data, key, iv, aesMode);
9108
+ const decryptResult = this.flush();
9109
+ if (decryptResult) {
9110
+ return decryptResult.buffer;
9111
+ }
9021
9112
  }
9022
- throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
9113
+ throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
9023
9114
  }
9024
9115
  getValidChunk(data) {
9025
9116
  let currentChunk = data;
@@ -9070,7 +9161,7 @@ const State = {
9070
9161
  };
9071
9162
  class BaseStreamController extends TaskLoop {
9072
9163
  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
9073
- super();
9164
+ super(logPrefix, hls.logger);
9074
9165
  this.hls = void 0;
9075
9166
  this.fragPrevious = null;
9076
9167
  this.fragCurrent = null;
@@ -9095,22 +9186,98 @@ class BaseStreamController extends TaskLoop {
9095
9186
  this.startFragRequested = false;
9096
9187
  this.decrypter = void 0;
9097
9188
  this.initPTS = [];
9098
- this.onvseeking = null;
9099
- this.onvended = null;
9100
- this.logPrefix = '';
9101
- this.log = void 0;
9102
- this.warn = void 0;
9189
+ this.buffering = true;
9190
+ this.loadingParts = false;
9191
+ this.onMediaSeeking = () => {
9192
+ const {
9193
+ config,
9194
+ fragCurrent,
9195
+ media,
9196
+ mediaBuffer,
9197
+ state
9198
+ } = this;
9199
+ const currentTime = media ? media.currentTime : 0;
9200
+ const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9201
+ this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9202
+ if (this.state === State.ENDED) {
9203
+ this.resetLoadingState();
9204
+ } else if (fragCurrent) {
9205
+ // Seeking while frag load is in progress
9206
+ const tolerance = config.maxFragLookUpTolerance;
9207
+ const fragStartOffset = fragCurrent.start - tolerance;
9208
+ const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9209
+ // if seeking out of buffered range or into new one
9210
+ if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9211
+ const pastFragment = currentTime > fragEndOffset;
9212
+ // if the seek position is outside the current fragment range
9213
+ if (currentTime < fragStartOffset || pastFragment) {
9214
+ if (pastFragment && fragCurrent.loader) {
9215
+ this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9216
+ fragCurrent.abortRequests();
9217
+ this.resetLoadingState();
9218
+ }
9219
+ this.fragPrevious = null;
9220
+ }
9221
+ }
9222
+ }
9223
+ if (media) {
9224
+ // Remove gap fragments
9225
+ this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9226
+ this.lastCurrentTime = currentTime;
9227
+ if (!this.loadingParts) {
9228
+ const bufferEnd = Math.max(bufferInfo.end, currentTime);
9229
+ const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
9230
+ if (shouldLoadParts) {
9231
+ this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
9232
+ this.loadingParts = shouldLoadParts;
9233
+ }
9234
+ }
9235
+ }
9236
+
9237
+ // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9238
+ if (!this.loadedmetadata && !bufferInfo.len) {
9239
+ this.nextLoadPosition = this.startPosition = currentTime;
9240
+ }
9241
+
9242
+ // Async tick to speed up processing
9243
+ this.tickImmediate();
9244
+ };
9245
+ this.onMediaEnded = () => {
9246
+ // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9247
+ this.startPosition = this.lastCurrentTime = 0;
9248
+ if (this.playlistType === PlaylistLevelType.MAIN) {
9249
+ this.hls.trigger(Events.MEDIA_ENDED, {
9250
+ stalled: false
9251
+ });
9252
+ }
9253
+ };
9103
9254
  this.playlistType = playlistType;
9104
- this.logPrefix = logPrefix;
9105
- this.log = logger.log.bind(logger, `${logPrefix}:`);
9106
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
9107
9255
  this.hls = hls;
9108
9256
  this.fragmentLoader = new FragmentLoader(hls.config);
9109
9257
  this.keyLoader = keyLoader;
9110
9258
  this.fragmentTracker = fragmentTracker;
9111
9259
  this.config = hls.config;
9112
9260
  this.decrypter = new Decrypter(hls.config);
9261
+ }
9262
+ registerListeners() {
9263
+ const {
9264
+ hls
9265
+ } = this;
9266
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
9267
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
9268
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
9113
9269
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9270
+ hls.on(Events.ERROR, this.onError, this);
9271
+ }
9272
+ unregisterListeners() {
9273
+ const {
9274
+ hls
9275
+ } = this;
9276
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
9277
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
9278
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
9279
+ hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9280
+ hls.off(Events.ERROR, this.onError, this);
9114
9281
  }
9115
9282
  doTick() {
9116
9283
  this.onTickEnd();
@@ -9134,6 +9301,12 @@ class BaseStreamController extends TaskLoop {
9134
9301
  this.clearNextTick();
9135
9302
  this.state = State.STOPPED;
9136
9303
  }
9304
+ pauseBuffering() {
9305
+ this.buffering = false;
9306
+ }
9307
+ resumeBuffering() {
9308
+ this.buffering = true;
9309
+ }
9137
9310
  _streamEnded(bufferInfo, levelDetails) {
9138
9311
  // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
9139
9312
  // of nothing loading/loaded return false
@@ -9164,10 +9337,8 @@ class BaseStreamController extends TaskLoop {
9164
9337
  }
9165
9338
  onMediaAttached(event, data) {
9166
9339
  const media = this.media = this.mediaBuffer = data.media;
9167
- this.onvseeking = this.onMediaSeeking.bind(this);
9168
- this.onvended = this.onMediaEnded.bind(this);
9169
- media.addEventListener('seeking', this.onvseeking);
9170
- media.addEventListener('ended', this.onvended);
9340
+ media.addEventListener('seeking', this.onMediaSeeking);
9341
+ media.addEventListener('ended', this.onMediaEnded);
9171
9342
  const config = this.config;
9172
9343
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
9173
9344
  this.startLoad(config.startPosition);
@@ -9181,10 +9352,9 @@ class BaseStreamController extends TaskLoop {
9181
9352
  }
9182
9353
 
9183
9354
  // remove video listeners
9184
- if (media && this.onvseeking && this.onvended) {
9185
- media.removeEventListener('seeking', this.onvseeking);
9186
- media.removeEventListener('ended', this.onvended);
9187
- this.onvseeking = this.onvended = null;
9355
+ if (media) {
9356
+ media.removeEventListener('seeking', this.onMediaSeeking);
9357
+ media.removeEventListener('ended', this.onMediaEnded);
9188
9358
  }
9189
9359
  if (this.keyLoader) {
9190
9360
  this.keyLoader.detach();
@@ -9194,56 +9364,8 @@ class BaseStreamController extends TaskLoop {
9194
9364
  this.fragmentTracker.removeAllFragments();
9195
9365
  this.stopLoad();
9196
9366
  }
9197
- onMediaSeeking() {
9198
- const {
9199
- config,
9200
- fragCurrent,
9201
- media,
9202
- mediaBuffer,
9203
- state
9204
- } = this;
9205
- const currentTime = media ? media.currentTime : 0;
9206
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9207
- this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9208
- if (this.state === State.ENDED) {
9209
- this.resetLoadingState();
9210
- } else if (fragCurrent) {
9211
- // Seeking while frag load is in progress
9212
- const tolerance = config.maxFragLookUpTolerance;
9213
- const fragStartOffset = fragCurrent.start - tolerance;
9214
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9215
- // if seeking out of buffered range or into new one
9216
- if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9217
- const pastFragment = currentTime > fragEndOffset;
9218
- // if the seek position is outside the current fragment range
9219
- if (currentTime < fragStartOffset || pastFragment) {
9220
- if (pastFragment && fragCurrent.loader) {
9221
- this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9222
- fragCurrent.abortRequests();
9223
- this.resetLoadingState();
9224
- }
9225
- this.fragPrevious = null;
9226
- }
9227
- }
9228
- }
9229
- if (media) {
9230
- // Remove gap fragments
9231
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9232
- this.lastCurrentTime = currentTime;
9233
- }
9234
-
9235
- // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9236
- if (!this.loadedmetadata && !bufferInfo.len) {
9237
- this.nextLoadPosition = this.startPosition = currentTime;
9238
- }
9239
-
9240
- // Async tick to speed up processing
9241
- this.tickImmediate();
9242
- }
9243
- onMediaEnded() {
9244
- // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9245
- this.startPosition = this.lastCurrentTime = 0;
9246
- }
9367
+ onManifestLoading() {}
9368
+ onError(event, data) {}
9247
9369
  onManifestLoaded(event, data) {
9248
9370
  this.startTimeOffset = data.startTimeOffset;
9249
9371
  this.initPTS = [];
@@ -9253,7 +9375,7 @@ class BaseStreamController extends TaskLoop {
9253
9375
  this.stopLoad();
9254
9376
  super.onHandlerDestroying();
9255
9377
  // @ts-ignore
9256
- this.hls = null;
9378
+ this.hls = this.onMediaSeeking = this.onMediaEnded = null;
9257
9379
  }
9258
9380
  onHandlerDestroyed() {
9259
9381
  this.state = State.STOPPED;
@@ -9384,10 +9506,10 @@ class BaseStreamController extends TaskLoop {
9384
9506
  const decryptData = frag.decryptdata;
9385
9507
 
9386
9508
  // check to see if the payload needs to be decrypted
9387
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
9509
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
9388
9510
  const startTime = self.performance.now();
9389
9511
  // decrypt init segment data
9390
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
9512
+ return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
9391
9513
  hls.trigger(Events.ERROR, {
9392
9514
  type: ErrorTypes.MEDIA_ERROR,
9393
9515
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -9499,7 +9621,7 @@ class BaseStreamController extends TaskLoop {
9499
9621
  }
9500
9622
  let keyLoadingPromise = null;
9501
9623
  if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
9502
- this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
9624
+ this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
9503
9625
  this.state = State.KEY_LOADING;
9504
9626
  this.fragCurrent = frag;
9505
9627
  keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
@@ -9520,8 +9642,16 @@ class BaseStreamController extends TaskLoop {
9520
9642
  } else if (!frag.encrypted && details.encryptedFragments.length) {
9521
9643
  this.keyLoader.loadClear(frag, details.encryptedFragments);
9522
9644
  }
9645
+ const fragPrevious = this.fragPrevious;
9646
+ if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
9647
+ const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
9648
+ if (shouldLoadParts !== this.loadingParts) {
9649
+ this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
9650
+ this.loadingParts = shouldLoadParts;
9651
+ }
9652
+ }
9523
9653
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
9524
- if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
9654
+ if (this.loadingParts && frag.sn !== 'initSegment') {
9525
9655
  const partList = details.partList;
9526
9656
  if (partList && progressCallback) {
9527
9657
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -9530,7 +9660,7 @@ class BaseStreamController extends TaskLoop {
9530
9660
  const partIndex = this.getNextPart(partList, frag, targetBufferTime);
9531
9661
  if (partIndex > -1) {
9532
9662
  const part = partList[partIndex];
9533
- this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
9663
+ 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))}`);
9534
9664
  this.nextLoadPosition = part.start + part.duration;
9535
9665
  this.state = State.FRAG_LOADING;
9536
9666
  let _result;
@@ -9559,7 +9689,14 @@ class BaseStreamController extends TaskLoop {
9559
9689
  }
9560
9690
  }
9561
9691
  }
9562
- 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))}`);
9692
+ if (frag.sn !== 'initSegment' && this.loadingParts) {
9693
+ this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
9694
+ this.loadingParts = false;
9695
+ } else if (!frag.url) {
9696
+ // Selected fragment hint for part but not loading parts
9697
+ return Promise.resolve(null);
9698
+ }
9699
+ this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
9563
9700
  // Don't update nextLoadPosition for fragments which are not buffered
9564
9701
  if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
9565
9702
  this.nextLoadPosition = frag.start + frag.duration;
@@ -9657,8 +9794,36 @@ class BaseStreamController extends TaskLoop {
9657
9794
  if (part) {
9658
9795
  part.stats.parsing.end = now;
9659
9796
  }
9797
+ // See if part loading should be disabled/enabled based on buffer and playback position.
9798
+ if (frag.sn !== 'initSegment') {
9799
+ const levelDetails = this.getLevelDetails();
9800
+ const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
9801
+ const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
9802
+ if (shouldLoadParts !== this.loadingParts) {
9803
+ this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
9804
+ this.loadingParts = shouldLoadParts;
9805
+ }
9806
+ }
9660
9807
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
9661
9808
  }
9809
+ shouldLoadParts(details, bufferEnd) {
9810
+ if (this.config.lowLatencyMode) {
9811
+ if (!details) {
9812
+ return this.loadingParts;
9813
+ }
9814
+ if (details != null && details.partList) {
9815
+ var _details$fragmentHint;
9816
+ // Buffer must be ahead of first part + duration of parts after last segment
9817
+ // and playback must be at or past segment adjacent to part list
9818
+ const firstPart = details.partList[0];
9819
+ const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
9820
+ if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
9821
+ return true;
9822
+ }
9823
+ }
9824
+ }
9825
+ return false;
9826
+ }
9662
9827
  getCurrentContext(chunkMeta) {
9663
9828
  const {
9664
9829
  levels,
@@ -9807,7 +9972,8 @@ class BaseStreamController extends TaskLoop {
9807
9972
  config
9808
9973
  } = this;
9809
9974
  const start = fragments[0].start;
9810
- let frag;
9975
+ const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
9976
+ let frag = null;
9811
9977
  if (levelDetails.live) {
9812
9978
  const initialLiveManifestSize = config.initialLiveManifestSize;
9813
9979
  if (fragLen < initialLiveManifestSize) {
@@ -9819,6 +9985,10 @@ class BaseStreamController extends TaskLoop {
9819
9985
  // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
9820
9986
  // we get the fragment matching that start time
9821
9987
  if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
9988
+ if (canLoadParts && !this.loadingParts) {
9989
+ this.log(`LL-Part loading ON for initial live fragment`);
9990
+ this.loadingParts = true;
9991
+ }
9822
9992
  frag = this.getInitialLiveFragment(levelDetails, fragments);
9823
9993
  this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
9824
9994
  }
@@ -9829,7 +9999,7 @@ class BaseStreamController extends TaskLoop {
9829
9999
 
9830
10000
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
9831
10001
  if (!frag) {
9832
- const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
10002
+ const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
9833
10003
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
9834
10004
  }
9835
10005
  return this.mapToInitFragWhenRequired(frag);
@@ -9951,7 +10121,7 @@ class BaseStreamController extends TaskLoop {
9951
10121
  } = levelDetails;
9952
10122
  const tolerance = config.maxFragLookUpTolerance;
9953
10123
  const partList = levelDetails.partList;
9954
- const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
10124
+ const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
9955
10125
  if (loadingParts && fragmentHint && !this.bitrateTest) {
9956
10126
  // Include incomplete fragment with parts at end
9957
10127
  fragments = fragments.concat(fragmentHint);
@@ -10144,7 +10314,7 @@ class BaseStreamController extends TaskLoop {
10144
10314
  errorAction.resolved = true;
10145
10315
  }
10146
10316
  } else {
10147
- logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10317
+ this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10148
10318
  return;
10149
10319
  }
10150
10320
  } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
@@ -10553,6 +10723,7 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
10553
10723
  */
10554
10724
  function getAudioConfig(observer, data, offset, audioCodec) {
10555
10725
  let adtsObjectType;
10726
+ let originalAdtsObjectType;
10556
10727
  let adtsExtensionSamplingIndex;
10557
10728
  let adtsChannelConfig;
10558
10729
  let config;
@@ -10560,7 +10731,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10560
10731
  const manifestCodec = audioCodec;
10561
10732
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
10562
10733
  // byte 2
10563
- adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10734
+ adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10564
10735
  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
10565
10736
  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
10566
10737
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
@@ -10577,8 +10748,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10577
10748
  // byte 3
10578
10749
  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
10579
10750
  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
10580
- // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
10581
- if (/firefox/i.test(userAgent)) {
10751
+ // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
10752
+ if (/firefox|palemoon/i.test(userAgent)) {
10582
10753
  if (adtsSamplingIndex >= 6) {
10583
10754
  adtsObjectType = 5;
10584
10755
  config = new Array(4);
@@ -10672,6 +10843,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10672
10843
  samplerate: adtsSamplingRates[adtsSamplingIndex],
10673
10844
  channelCount: adtsChannelConfig,
10674
10845
  codec: 'mp4a.40.' + adtsObjectType,
10846
+ parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
10675
10847
  manifestCodec
10676
10848
  };
10677
10849
  }
@@ -10726,7 +10898,8 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
10726
10898
  track.channelCount = config.channelCount;
10727
10899
  track.codec = config.codec;
10728
10900
  track.manifestCodec = config.manifestCodec;
10729
- logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10901
+ track.parsedCodec = config.parsedCodec;
10902
+ logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10730
10903
  }
10731
10904
  }
10732
10905
  function getFrameDuration(samplerate) {
@@ -11317,11 +11490,115 @@ class BaseVideoParser {
11317
11490
  logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
11318
11491
  }
11319
11492
  }
11320
- }
11321
-
11322
- /**
11323
- * Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
11324
- */
11493
+ parseNALu(track, array) {
11494
+ const len = array.byteLength;
11495
+ let state = track.naluState || 0;
11496
+ const lastState = state;
11497
+ const units = [];
11498
+ let i = 0;
11499
+ let value;
11500
+ let overflow;
11501
+ let unitType;
11502
+ let lastUnitStart = -1;
11503
+ let lastUnitType = 0;
11504
+ // logger.log('PES:' + Hex.hexDump(array));
11505
+
11506
+ if (state === -1) {
11507
+ // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
11508
+ lastUnitStart = 0;
11509
+ // NALu type is value read from offset 0
11510
+ lastUnitType = this.getNALuType(array, 0);
11511
+ state = 0;
11512
+ i = 1;
11513
+ }
11514
+ while (i < len) {
11515
+ value = array[i++];
11516
+ // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
11517
+ if (!state) {
11518
+ state = value ? 0 : 1;
11519
+ continue;
11520
+ }
11521
+ if (state === 1) {
11522
+ state = value ? 0 : 2;
11523
+ continue;
11524
+ }
11525
+ // here we have state either equal to 2 or 3
11526
+ if (!value) {
11527
+ state = 3;
11528
+ } else if (value === 1) {
11529
+ overflow = i - state - 1;
11530
+ if (lastUnitStart >= 0) {
11531
+ const unit = {
11532
+ data: array.subarray(lastUnitStart, overflow),
11533
+ type: lastUnitType
11534
+ };
11535
+ // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
11536
+ units.push(unit);
11537
+ } else {
11538
+ // lastUnitStart is undefined => this is the first start code found in this PES packet
11539
+ // first check if start code delimiter is overlapping between 2 PES packets,
11540
+ // ie it started in last packet (lastState not zero)
11541
+ // and ended at the beginning of this PES packet (i <= 4 - lastState)
11542
+ const lastUnit = this.getLastNalUnit(track.samples);
11543
+ if (lastUnit) {
11544
+ if (lastState && i <= 4 - lastState) {
11545
+ // start delimiter overlapping between PES packets
11546
+ // strip start delimiter bytes from the end of last NAL unit
11547
+ // check if lastUnit had a state different from zero
11548
+ if (lastUnit.state) {
11549
+ // strip last bytes
11550
+ lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
11551
+ }
11552
+ }
11553
+ // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
11554
+
11555
+ if (overflow > 0) {
11556
+ // logger.log('first NALU found with overflow:' + overflow);
11557
+ lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
11558
+ lastUnit.state = 0;
11559
+ }
11560
+ }
11561
+ }
11562
+ // check if we can read unit type
11563
+ if (i < len) {
11564
+ unitType = this.getNALuType(array, i);
11565
+ // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
11566
+ lastUnitStart = i;
11567
+ lastUnitType = unitType;
11568
+ state = 0;
11569
+ } else {
11570
+ // not enough byte to read unit type. let's read it on next PES parsing
11571
+ state = -1;
11572
+ }
11573
+ } else {
11574
+ state = 0;
11575
+ }
11576
+ }
11577
+ if (lastUnitStart >= 0 && state >= 0) {
11578
+ const unit = {
11579
+ data: array.subarray(lastUnitStart, len),
11580
+ type: lastUnitType,
11581
+ state: state
11582
+ };
11583
+ units.push(unit);
11584
+ // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
11585
+ }
11586
+ // no NALu found
11587
+ if (units.length === 0) {
11588
+ // append pes.data to previous NAL unit
11589
+ const lastUnit = this.getLastNalUnit(track.samples);
11590
+ if (lastUnit) {
11591
+ lastUnit.data = appendUint8Array(lastUnit.data, array);
11592
+ }
11593
+ }
11594
+ track.naluState = state;
11595
+ return units;
11596
+ }
11597
+ }
11598
+
11599
+ /**
11600
+ * Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
11601
+ */
11325
11602
 
11326
11603
  class ExpGolomb {
11327
11604
  constructor(data) {
@@ -11459,21 +11736,171 @@ class ExpGolomb {
11459
11736
  readUInt() {
11460
11737
  return this.readBits(32);
11461
11738
  }
11739
+ }
11740
+
11741
+ class AvcVideoParser extends BaseVideoParser {
11742
+ parsePES(track, textTrack, pes, last, duration) {
11743
+ const units = this.parseNALu(track, pes.data);
11744
+ let VideoSample = this.VideoSample;
11745
+ let push;
11746
+ let spsfound = false;
11747
+ // free pes.data to save up some memory
11748
+ pes.data = null;
11749
+
11750
+ // if new NAL units found and last sample still there, let's push ...
11751
+ // this helps parsing streams with missing AUD (only do this if AUD never found)
11752
+ if (VideoSample && units.length && !track.audFound) {
11753
+ this.pushAccessUnit(VideoSample, track);
11754
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
11755
+ }
11756
+ units.forEach(unit => {
11757
+ var _VideoSample2;
11758
+ switch (unit.type) {
11759
+ // NDR
11760
+ case 1:
11761
+ {
11762
+ let iskey = false;
11763
+ push = true;
11764
+ const data = unit.data;
11765
+ // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
11766
+ if (spsfound && data.length > 4) {
11767
+ // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
11768
+ const sliceType = this.readSliceType(data);
11769
+ // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
11770
+ // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
11771
+ // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
11772
+ // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
11773
+ // if (sliceType === 2 || sliceType === 7) {
11774
+ if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
11775
+ iskey = true;
11776
+ }
11777
+ }
11778
+ if (iskey) {
11779
+ var _VideoSample;
11780
+ // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
11781
+ if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
11782
+ this.pushAccessUnit(VideoSample, track);
11783
+ VideoSample = this.VideoSample = null;
11784
+ }
11785
+ }
11786
+ if (!VideoSample) {
11787
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11788
+ }
11789
+ VideoSample.frame = true;
11790
+ VideoSample.key = iskey;
11791
+ break;
11792
+ // IDR
11793
+ }
11794
+ case 5:
11795
+ push = true;
11796
+ // handle PES not starting with AUD
11797
+ // if we have frame data already, that cannot belong to the same frame, so force a push
11798
+ if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
11799
+ this.pushAccessUnit(VideoSample, track);
11800
+ VideoSample = this.VideoSample = null;
11801
+ }
11802
+ if (!VideoSample) {
11803
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
11804
+ }
11805
+ VideoSample.key = true;
11806
+ VideoSample.frame = true;
11807
+ break;
11808
+ // SEI
11809
+ case 6:
11810
+ {
11811
+ push = true;
11812
+ parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
11813
+ break;
11814
+ // SPS
11815
+ }
11816
+ case 7:
11817
+ {
11818
+ var _track$pixelRatio, _track$pixelRatio2;
11819
+ push = true;
11820
+ spsfound = true;
11821
+ const sps = unit.data;
11822
+ const config = this.readSPS(sps);
11823
+ if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) {
11824
+ track.width = config.width;
11825
+ track.height = config.height;
11826
+ track.pixelRatio = config.pixelRatio;
11827
+ track.sps = [sps];
11828
+ track.duration = duration;
11829
+ const codecarray = sps.subarray(1, 4);
11830
+ let codecstring = 'avc1.';
11831
+ for (let i = 0; i < 3; i++) {
11832
+ let h = codecarray[i].toString(16);
11833
+ if (h.length < 2) {
11834
+ h = '0' + h;
11835
+ }
11836
+ codecstring += h;
11837
+ }
11838
+ track.codec = codecstring;
11839
+ }
11840
+ break;
11841
+ }
11842
+ // PPS
11843
+ case 8:
11844
+ push = true;
11845
+ track.pps = [unit.data];
11846
+ break;
11847
+ // AUD
11848
+ case 9:
11849
+ push = true;
11850
+ track.audFound = true;
11851
+ if (VideoSample) {
11852
+ this.pushAccessUnit(VideoSample, track);
11853
+ }
11854
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
11855
+ break;
11856
+ // Filler Data
11857
+ case 12:
11858
+ push = true;
11859
+ break;
11860
+ default:
11861
+ push = false;
11862
+ if (VideoSample) {
11863
+ VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
11864
+ }
11865
+ break;
11866
+ }
11867
+ if (VideoSample && push) {
11868
+ const units = VideoSample.units;
11869
+ units.push(unit);
11870
+ }
11871
+ });
11872
+ // if last PES packet, push samples
11873
+ if (last && VideoSample) {
11874
+ this.pushAccessUnit(VideoSample, track);
11875
+ this.VideoSample = null;
11876
+ }
11877
+ }
11878
+ getNALuType(data, offset) {
11879
+ return data[offset] & 0x1f;
11880
+ }
11881
+ readSliceType(data) {
11882
+ const eg = new ExpGolomb(data);
11883
+ // skip NALu type
11884
+ eg.readUByte();
11885
+ // discard first_mb_in_slice
11886
+ eg.readUEG();
11887
+ // return slice_type
11888
+ return eg.readUEG();
11889
+ }
11462
11890
 
11463
11891
  /**
11464
- * Advance the ExpGolomb decoder past a scaling list. The scaling
11465
- * list is optionally transmitted as part of a sequence parameter
11892
+ * The scaling list is optionally transmitted as part of a sequence parameter
11466
11893
  * set and is not relevant to transmuxing.
11467
11894
  * @param count the number of entries in this scaling list
11468
11895
  * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
11469
11896
  */
11470
- skipScalingList(count) {
11897
+ skipScalingList(count, reader) {
11471
11898
  let lastScale = 8;
11472
11899
  let nextScale = 8;
11473
11900
  let deltaScale;
11474
11901
  for (let j = 0; j < count; j++) {
11475
11902
  if (nextScale !== 0) {
11476
- deltaScale = this.readEG();
11903
+ deltaScale = reader.readEG();
11477
11904
  nextScale = (lastScale + deltaScale + 256) % 256;
11478
11905
  }
11479
11906
  lastScale = nextScale === 0 ? lastScale : nextScale;
@@ -11488,7 +11915,8 @@ class ExpGolomb {
11488
11915
  * sequence parameter set, including the dimensions of the
11489
11916
  * associated video frames.
11490
11917
  */
11491
- readSPS() {
11918
+ readSPS(sps) {
11919
+ const eg = new ExpGolomb(sps);
11492
11920
  let frameCropLeftOffset = 0;
11493
11921
  let frameCropRightOffset = 0;
11494
11922
  let frameCropTopOffset = 0;
@@ -11496,13 +11924,13 @@ class ExpGolomb {
11496
11924
  let numRefFramesInPicOrderCntCycle;
11497
11925
  let scalingListCount;
11498
11926
  let i;
11499
- const readUByte = this.readUByte.bind(this);
11500
- const readBits = this.readBits.bind(this);
11501
- const readUEG = this.readUEG.bind(this);
11502
- const readBoolean = this.readBoolean.bind(this);
11503
- const skipBits = this.skipBits.bind(this);
11504
- const skipEG = this.skipEG.bind(this);
11505
- const skipUEG = this.skipUEG.bind(this);
11927
+ const readUByte = eg.readUByte.bind(eg);
11928
+ const readBits = eg.readBits.bind(eg);
11929
+ const readUEG = eg.readUEG.bind(eg);
11930
+ const readBoolean = eg.readBoolean.bind(eg);
11931
+ const skipBits = eg.skipBits.bind(eg);
11932
+ const skipEG = eg.skipEG.bind(eg);
11933
+ const skipUEG = eg.skipUEG.bind(eg);
11506
11934
  const skipScalingList = this.skipScalingList.bind(this);
11507
11935
  readUByte();
11508
11936
  const profileIdc = readUByte(); // profile_idc
@@ -11527,9 +11955,9 @@ class ExpGolomb {
11527
11955
  if (readBoolean()) {
11528
11956
  // seq_scaling_list_present_flag[ i ]
11529
11957
  if (i < 6) {
11530
- skipScalingList(16);
11958
+ skipScalingList(16, eg);
11531
11959
  } else {
11532
- skipScalingList(64);
11960
+ skipScalingList(64, eg);
11533
11961
  }
11534
11962
  }
11535
11963
  }
@@ -11634,19 +12062,15 @@ class ExpGolomb {
11634
12062
  pixelRatio: pixelRatio
11635
12063
  };
11636
12064
  }
11637
- readSliceType() {
11638
- // skip NALu type
11639
- this.readUByte();
11640
- // discard first_mb_in_slice
11641
- this.readUEG();
11642
- // return slice_type
11643
- return this.readUEG();
11644
- }
11645
12065
  }
11646
12066
 
11647
- class AvcVideoParser extends BaseVideoParser {
11648
- parseAVCPES(track, textTrack, pes, last, duration) {
11649
- const units = this.parseAVCNALu(track, pes.data);
12067
+ class HevcVideoParser extends BaseVideoParser {
12068
+ constructor(...args) {
12069
+ super(...args);
12070
+ this.initVPS = null;
12071
+ }
12072
+ parsePES(track, textTrack, pes, last, duration) {
12073
+ const units = this.parseNALu(track, pes.data);
11650
12074
  let VideoSample = this.VideoSample;
11651
12075
  let push;
11652
12076
  let spsfound = false;
@@ -11662,42 +12086,49 @@ class AvcVideoParser extends BaseVideoParser {
11662
12086
  units.forEach(unit => {
11663
12087
  var _VideoSample2;
11664
12088
  switch (unit.type) {
11665
- // NDR
12089
+ // NON-IDR, NON RANDOM ACCESS SLICE
12090
+ case 0:
11666
12091
  case 1:
11667
- {
11668
- let iskey = false;
11669
- push = true;
11670
- const data = unit.data;
11671
- // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
11672
- if (spsfound && data.length > 4) {
11673
- // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
11674
- const sliceType = new ExpGolomb(data).readSliceType();
11675
- // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
11676
- // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
11677
- // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
11678
- // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
11679
- // if (sliceType === 2 || sliceType === 7) {
11680
- if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
11681
- iskey = true;
11682
- }
11683
- }
11684
- if (iskey) {
11685
- var _VideoSample;
11686
- // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
11687
- if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
11688
- this.pushAccessUnit(VideoSample, track);
11689
- VideoSample = this.VideoSample = null;
11690
- }
11691
- }
11692
- if (!VideoSample) {
11693
- VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12092
+ case 2:
12093
+ case 3:
12094
+ case 4:
12095
+ case 5:
12096
+ case 6:
12097
+ case 7:
12098
+ case 8:
12099
+ case 9:
12100
+ if (!VideoSample) {
12101
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12102
+ }
12103
+ VideoSample.frame = true;
12104
+ push = true;
12105
+ break;
12106
+
12107
+ // CRA, BLA (random access picture)
12108
+ case 16:
12109
+ case 17:
12110
+ case 18:
12111
+ case 21:
12112
+ push = true;
12113
+ if (spsfound) {
12114
+ var _VideoSample;
12115
+ // handle PES not starting with AUD
12116
+ // if we have frame data already, that cannot belong to the same frame, so force a push
12117
+ if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
12118
+ this.pushAccessUnit(VideoSample, track);
12119
+ VideoSample = this.VideoSample = null;
11694
12120
  }
11695
- VideoSample.frame = true;
11696
- VideoSample.key = iskey;
11697
- break;
11698
- // IDR
11699
12121
  }
11700
- case 5:
12122
+ if (!VideoSample) {
12123
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12124
+ }
12125
+ VideoSample.key = true;
12126
+ VideoSample.frame = true;
12127
+ break;
12128
+
12129
+ // IDR
12130
+ case 19:
12131
+ case 20:
11701
12132
  push = true;
11702
12133
  // handle PES not starting with AUD
11703
12134
  // if we have frame data already, that cannot belong to the same frame, so force a push
@@ -11711,180 +12142,518 @@ class AvcVideoParser extends BaseVideoParser {
11711
12142
  VideoSample.key = true;
11712
12143
  VideoSample.frame = true;
11713
12144
  break;
12145
+
11714
12146
  // SEI
11715
- case 6:
11716
- {
11717
- push = true;
11718
- parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
11719
- break;
11720
- // SPS
11721
- }
11722
- case 7:
11723
- {
11724
- var _track$pixelRatio, _track$pixelRatio2;
11725
- push = true;
11726
- spsfound = true;
11727
- const sps = unit.data;
11728
- const expGolombDecoder = new ExpGolomb(sps);
11729
- const config = expGolombDecoder.readSPS();
11730
- 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]) {
11731
- track.width = config.width;
11732
- track.height = config.height;
11733
- track.pixelRatio = config.pixelRatio;
11734
- track.sps = [sps];
11735
- track.duration = duration;
11736
- const codecarray = sps.subarray(1, 4);
11737
- let codecstring = 'avc1.';
11738
- for (let i = 0; i < 3; i++) {
11739
- let h = codecarray[i].toString(16);
11740
- if (h.length < 2) {
11741
- h = '0' + h;
11742
- }
11743
- codecstring += h;
11744
- }
11745
- track.codec = codecstring;
11746
- }
11747
- break;
11748
- }
11749
- // PPS
11750
- case 8:
12147
+ case 39:
11751
12148
  push = true;
11752
- track.pps = [unit.data];
11753
- break;
11754
- // AUD
11755
- case 9:
11756
- push = true;
11757
- track.audFound = true;
11758
- if (VideoSample) {
11759
- this.pushAccessUnit(VideoSample, track);
11760
- }
11761
- VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12149
+ parseSEIMessageFromNALu(unit.data, 2,
12150
+ // NALu header size
12151
+ pes.pts, textTrack.samples);
11762
12152
  break;
11763
- // Filler Data
11764
- case 12:
12153
+
12154
+ // VPS
12155
+ case 32:
11765
12156
  push = true;
11766
- break;
11767
- default:
11768
- push = false;
11769
- if (VideoSample) {
11770
- VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
12157
+ if (!track.vps) {
12158
+ const config = this.readVPS(unit.data);
12159
+ track.params = _objectSpread2({}, config);
12160
+ this.initVPS = unit.data;
11771
12161
  }
12162
+ track.vps = [unit.data];
11772
12163
  break;
11773
- }
11774
- if (VideoSample && push) {
11775
- const units = VideoSample.units;
11776
- units.push(unit);
11777
- }
11778
- });
11779
- // if last PES packet, push samples
11780
- if (last && VideoSample) {
11781
- this.pushAccessUnit(VideoSample, track);
11782
- this.VideoSample = null;
11783
- }
11784
- }
11785
- parseAVCNALu(track, array) {
11786
- const len = array.byteLength;
11787
- let state = track.naluState || 0;
11788
- const lastState = state;
11789
- const units = [];
11790
- let i = 0;
11791
- let value;
11792
- let overflow;
11793
- let unitType;
11794
- let lastUnitStart = -1;
11795
- let lastUnitType = 0;
11796
- // logger.log('PES:' + Hex.hexDump(array));
11797
12164
 
11798
- if (state === -1) {
11799
- // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
11800
- lastUnitStart = 0;
11801
- // NALu type is value read from offset 0
11802
- lastUnitType = array[0] & 0x1f;
11803
- state = 0;
11804
- i = 1;
11805
- }
11806
- while (i < len) {
11807
- value = array[i++];
11808
- // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
11809
- if (!state) {
11810
- state = value ? 0 : 1;
11811
- continue;
11812
- }
11813
- if (state === 1) {
11814
- state = value ? 0 : 2;
11815
- continue;
11816
- }
11817
- // here we have state either equal to 2 or 3
11818
- if (!value) {
11819
- state = 3;
11820
- } else if (value === 1) {
11821
- overflow = i - state - 1;
11822
- if (lastUnitStart >= 0) {
11823
- const unit = {
11824
- data: array.subarray(lastUnitStart, overflow),
11825
- type: lastUnitType
11826
- };
11827
- // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
11828
- units.push(unit);
11829
- } else {
11830
- // lastUnitStart is undefined => this is the first start code found in this PES packet
11831
- // first check if start code delimiter is overlapping between 2 PES packets,
11832
- // ie it started in last packet (lastState not zero)
11833
- // and ended at the beginning of this PES packet (i <= 4 - lastState)
11834
- const lastUnit = this.getLastNalUnit(track.samples);
11835
- if (lastUnit) {
11836
- if (lastState && i <= 4 - lastState) {
11837
- // start delimiter overlapping between PES packets
11838
- // strip start delimiter bytes from the end of last NAL unit
11839
- // check if lastUnit had a state different from zero
11840
- if (lastUnit.state) {
11841
- // strip last bytes
11842
- lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
12165
+ // SPS
12166
+ case 33:
12167
+ push = true;
12168
+ spsfound = true;
12169
+ if (typeof track.params === 'object') {
12170
+ if (track.vps !== undefined && track.vps[0] !== this.initVPS && track.sps !== undefined && !this.matchSPS(track.sps[0], unit.data)) {
12171
+ this.initVPS = track.vps[0];
12172
+ track.sps = track.pps = undefined;
12173
+ }
12174
+ if (!track.sps) {
12175
+ const config = this.readSPS(unit.data);
12176
+ track.width = config.width;
12177
+ track.height = config.height;
12178
+ track.pixelRatio = config.pixelRatio;
12179
+ track.duration = duration;
12180
+ track.codec = config.codecString;
12181
+ track.sps = [];
12182
+ for (const prop in config.params) {
12183
+ track.params[prop] = config.params[prop];
11843
12184
  }
11844
12185
  }
11845
- // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
12186
+ if (track.vps !== undefined && track.vps[0] === this.initVPS) {
12187
+ track.sps.push(unit.data);
12188
+ }
12189
+ }
12190
+ if (!VideoSample) {
12191
+ VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12192
+ }
12193
+ VideoSample.key = true;
12194
+ break;
11846
12195
 
11847
- if (overflow > 0) {
11848
- // logger.log('first NALU found with overflow:' + overflow);
11849
- lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
11850
- lastUnit.state = 0;
12196
+ // PPS
12197
+ case 34:
12198
+ push = true;
12199
+ if (typeof track.params === 'object') {
12200
+ if (!track.pps) {
12201
+ track.pps = [];
12202
+ const config = this.readPPS(unit.data);
12203
+ for (const prop in config) {
12204
+ track.params[prop] = config[prop];
12205
+ }
12206
+ }
12207
+ if (this.initVPS !== null || track.pps.length === 0) {
12208
+ track.pps.push(unit.data);
11851
12209
  }
11852
12210
  }
12211
+ break;
12212
+
12213
+ // ACCESS UNIT DELIMITER
12214
+ case 35:
12215
+ push = true;
12216
+ track.audFound = true;
12217
+ if (VideoSample) {
12218
+ this.pushAccessUnit(VideoSample, track);
12219
+ }
12220
+ VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12221
+ break;
12222
+ default:
12223
+ push = false;
12224
+ if (VideoSample) {
12225
+ VideoSample.debug += 'unknown or irrelevant NAL ' + unit.type + ' ';
12226
+ }
12227
+ break;
12228
+ }
12229
+ if (VideoSample && push) {
12230
+ const units = VideoSample.units;
12231
+ units.push(unit);
12232
+ }
12233
+ });
12234
+ // if last PES packet, push samples
12235
+ if (last && VideoSample) {
12236
+ this.pushAccessUnit(VideoSample, track);
12237
+ this.VideoSample = null;
12238
+ }
12239
+ }
12240
+ getNALuType(data, offset) {
12241
+ return (data[offset] & 0x7e) >>> 1;
12242
+ }
12243
+ ebsp2rbsp(arr) {
12244
+ const dst = new Uint8Array(arr.byteLength);
12245
+ let dstIdx = 0;
12246
+ for (let i = 0; i < arr.byteLength; i++) {
12247
+ if (i >= 2) {
12248
+ // Unescape: Skip 0x03 after 00 00
12249
+ if (arr[i] === 0x03 && arr[i - 1] === 0x00 && arr[i - 2] === 0x00) {
12250
+ continue;
11853
12251
  }
11854
- // check if we can read unit type
11855
- if (i < len) {
11856
- unitType = array[i] & 0x1f;
11857
- // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
11858
- lastUnitStart = i;
11859
- lastUnitType = unitType;
11860
- state = 0;
11861
- } else {
11862
- // not enough byte to read unit type. let's read it on next PES parsing
11863
- state = -1;
11864
- }
11865
- } else {
11866
- state = 0;
11867
12252
  }
12253
+ dst[dstIdx] = arr[i];
12254
+ dstIdx++;
11868
12255
  }
11869
- if (lastUnitStart >= 0 && state >= 0) {
11870
- const unit = {
11871
- data: array.subarray(lastUnitStart, len),
11872
- type: lastUnitType,
11873
- state: state
11874
- };
11875
- units.push(unit);
11876
- // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
12256
+ return new Uint8Array(dst.buffer, 0, dstIdx);
12257
+ }
12258
+ readVPS(vps) {
12259
+ const eg = new ExpGolomb(vps);
12260
+ // remove header
12261
+ eg.readUByte();
12262
+ eg.readUByte();
12263
+ eg.readBits(4); // video_parameter_set_id
12264
+ eg.skipBits(2);
12265
+ eg.readBits(6); // max_layers_minus1
12266
+ const max_sub_layers_minus1 = eg.readBits(3);
12267
+ const temporal_id_nesting_flag = eg.readBoolean();
12268
+ // ...vui fps can be here, but empty fps value is not critical for metadata
12269
+
12270
+ return {
12271
+ numTemporalLayers: max_sub_layers_minus1 + 1,
12272
+ temporalIdNested: temporal_id_nesting_flag
12273
+ };
12274
+ }
12275
+ readSPS(sps) {
12276
+ const eg = new ExpGolomb(this.ebsp2rbsp(sps));
12277
+ eg.readUByte();
12278
+ eg.readUByte();
12279
+ eg.readBits(4); //video_parameter_set_id
12280
+ const max_sub_layers_minus1 = eg.readBits(3);
12281
+ eg.readBoolean(); // temporal_id_nesting_flag
12282
+
12283
+ // profile_tier_level
12284
+ const general_profile_space = eg.readBits(2);
12285
+ const general_tier_flag = eg.readBoolean();
12286
+ const general_profile_idc = eg.readBits(5);
12287
+ const general_profile_compatibility_flags_1 = eg.readUByte();
12288
+ const general_profile_compatibility_flags_2 = eg.readUByte();
12289
+ const general_profile_compatibility_flags_3 = eg.readUByte();
12290
+ const general_profile_compatibility_flags_4 = eg.readUByte();
12291
+ const general_constraint_indicator_flags_1 = eg.readUByte();
12292
+ const general_constraint_indicator_flags_2 = eg.readUByte();
12293
+ const general_constraint_indicator_flags_3 = eg.readUByte();
12294
+ const general_constraint_indicator_flags_4 = eg.readUByte();
12295
+ const general_constraint_indicator_flags_5 = eg.readUByte();
12296
+ const general_constraint_indicator_flags_6 = eg.readUByte();
12297
+ const general_level_idc = eg.readUByte();
12298
+ const sub_layer_profile_present_flags = [];
12299
+ const sub_layer_level_present_flags = [];
12300
+ for (let i = 0; i < max_sub_layers_minus1; i++) {
12301
+ sub_layer_profile_present_flags.push(eg.readBoolean());
12302
+ sub_layer_level_present_flags.push(eg.readBoolean());
12303
+ }
12304
+ if (max_sub_layers_minus1 > 0) {
12305
+ for (let i = max_sub_layers_minus1; i < 8; i++) {
12306
+ eg.readBits(2);
12307
+ }
12308
+ }
12309
+ for (let i = 0; i < max_sub_layers_minus1; i++) {
12310
+ if (sub_layer_profile_present_flags[i]) {
12311
+ eg.readUByte(); // sub_layer_profile_space, sub_layer_tier_flag, sub_layer_profile_idc
12312
+ eg.readUByte();
12313
+ eg.readUByte();
12314
+ eg.readUByte();
12315
+ eg.readUByte(); // sub_layer_profile_compatibility_flag
12316
+ eg.readUByte();
12317
+ eg.readUByte();
12318
+ eg.readUByte();
12319
+ eg.readUByte();
12320
+ eg.readUByte();
12321
+ eg.readUByte();
12322
+ }
12323
+ if (sub_layer_level_present_flags[i]) {
12324
+ eg.readUByte();
12325
+ }
12326
+ }
12327
+ eg.readUEG(); // seq_parameter_set_id
12328
+ const chroma_format_idc = eg.readUEG();
12329
+ if (chroma_format_idc == 3) {
12330
+ eg.skipBits(1); //separate_colour_plane_flag
12331
+ }
12332
+ const pic_width_in_luma_samples = eg.readUEG();
12333
+ const pic_height_in_luma_samples = eg.readUEG();
12334
+ const conformance_window_flag = eg.readBoolean();
12335
+ let pic_left_offset = 0,
12336
+ pic_right_offset = 0,
12337
+ pic_top_offset = 0,
12338
+ pic_bottom_offset = 0;
12339
+ if (conformance_window_flag) {
12340
+ pic_left_offset += eg.readUEG();
12341
+ pic_right_offset += eg.readUEG();
12342
+ pic_top_offset += eg.readUEG();
12343
+ pic_bottom_offset += eg.readUEG();
12344
+ }
12345
+ const bit_depth_luma_minus8 = eg.readUEG();
12346
+ const bit_depth_chroma_minus8 = eg.readUEG();
12347
+ const log2_max_pic_order_cnt_lsb_minus4 = eg.readUEG();
12348
+ const sub_layer_ordering_info_present_flag = eg.readBoolean();
12349
+ for (let i = sub_layer_ordering_info_present_flag ? 0 : max_sub_layers_minus1; i <= max_sub_layers_minus1; i++) {
12350
+ eg.skipUEG(); // max_dec_pic_buffering_minus1[i]
12351
+ eg.skipUEG(); // max_num_reorder_pics[i]
12352
+ eg.skipUEG(); // max_latency_increase_plus1[i]
12353
+ }
12354
+ eg.skipUEG(); // log2_min_luma_coding_block_size_minus3
12355
+ eg.skipUEG(); // log2_diff_max_min_luma_coding_block_size
12356
+ eg.skipUEG(); // log2_min_transform_block_size_minus2
12357
+ eg.skipUEG(); // log2_diff_max_min_transform_block_size
12358
+ eg.skipUEG(); // max_transform_hierarchy_depth_inter
12359
+ eg.skipUEG(); // max_transform_hierarchy_depth_intra
12360
+ const scaling_list_enabled_flag = eg.readBoolean();
12361
+ if (scaling_list_enabled_flag) {
12362
+ const sps_scaling_list_data_present_flag = eg.readBoolean();
12363
+ if (sps_scaling_list_data_present_flag) {
12364
+ for (let sizeId = 0; sizeId < 4; sizeId++) {
12365
+ for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
12366
+ const scaling_list_pred_mode_flag = eg.readBoolean();
12367
+ if (!scaling_list_pred_mode_flag) {
12368
+ eg.readUEG(); // scaling_list_pred_matrix_id_delta
12369
+ } else {
12370
+ const coefNum = Math.min(64, 1 << 4 + (sizeId << 1));
12371
+ if (sizeId > 1) {
12372
+ eg.readEG();
12373
+ }
12374
+ for (let i = 0; i < coefNum; i++) {
12375
+ eg.readEG();
12376
+ }
12377
+ }
12378
+ }
12379
+ }
12380
+ }
11877
12381
  }
11878
- // no NALu found
11879
- if (units.length === 0) {
11880
- // append pes.data to previous NAL unit
11881
- const lastUnit = this.getLastNalUnit(track.samples);
11882
- if (lastUnit) {
11883
- lastUnit.data = appendUint8Array(lastUnit.data, array);
12382
+ eg.readBoolean(); // amp_enabled_flag
12383
+ eg.readBoolean(); // sample_adaptive_offset_enabled_flag
12384
+ const pcm_enabled_flag = eg.readBoolean();
12385
+ if (pcm_enabled_flag) {
12386
+ eg.readUByte();
12387
+ eg.skipUEG();
12388
+ eg.skipUEG();
12389
+ eg.readBoolean();
12390
+ }
12391
+ const num_short_term_ref_pic_sets = eg.readUEG();
12392
+ let num_delta_pocs = 0;
12393
+ for (let i = 0; i < num_short_term_ref_pic_sets; i++) {
12394
+ let inter_ref_pic_set_prediction_flag = false;
12395
+ if (i !== 0) {
12396
+ inter_ref_pic_set_prediction_flag = eg.readBoolean();
12397
+ }
12398
+ if (inter_ref_pic_set_prediction_flag) {
12399
+ if (i === num_short_term_ref_pic_sets) {
12400
+ eg.readUEG();
12401
+ }
12402
+ eg.readBoolean();
12403
+ eg.readUEG();
12404
+ let next_num_delta_pocs = 0;
12405
+ for (let j = 0; j <= num_delta_pocs; j++) {
12406
+ const used_by_curr_pic_flag = eg.readBoolean();
12407
+ let use_delta_flag = false;
12408
+ if (!used_by_curr_pic_flag) {
12409
+ use_delta_flag = eg.readBoolean();
12410
+ }
12411
+ if (used_by_curr_pic_flag || use_delta_flag) {
12412
+ next_num_delta_pocs++;
12413
+ }
12414
+ }
12415
+ num_delta_pocs = next_num_delta_pocs;
12416
+ } else {
12417
+ const num_negative_pics = eg.readUEG();
12418
+ const num_positive_pics = eg.readUEG();
12419
+ num_delta_pocs = num_negative_pics + num_positive_pics;
12420
+ for (let j = 0; j < num_negative_pics; j++) {
12421
+ eg.readUEG();
12422
+ eg.readBoolean();
12423
+ }
12424
+ for (let j = 0; j < num_positive_pics; j++) {
12425
+ eg.readUEG();
12426
+ eg.readBoolean();
12427
+ }
12428
+ }
12429
+ }
12430
+ const long_term_ref_pics_present_flag = eg.readBoolean();
12431
+ if (long_term_ref_pics_present_flag) {
12432
+ const num_long_term_ref_pics_sps = eg.readUEG();
12433
+ for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
12434
+ for (let j = 0; j < log2_max_pic_order_cnt_lsb_minus4 + 4; j++) {
12435
+ eg.readBits(1);
12436
+ }
12437
+ eg.readBits(1);
12438
+ }
12439
+ }
12440
+ let min_spatial_segmentation_idc = 0;
12441
+ let sar_width = 1,
12442
+ sar_height = 1;
12443
+ let fps_fixed = true,
12444
+ fps_den = 1,
12445
+ fps_num = 0;
12446
+ eg.readBoolean(); // sps_temporal_mvp_enabled_flag
12447
+ eg.readBoolean(); // strong_intra_smoothing_enabled_flag
12448
+ let default_display_window_flag = false;
12449
+ const vui_parameters_present_flag = eg.readBoolean();
12450
+ if (vui_parameters_present_flag) {
12451
+ const aspect_ratio_info_present_flag = eg.readBoolean();
12452
+ if (aspect_ratio_info_present_flag) {
12453
+ const aspect_ratio_idc = eg.readUByte();
12454
+ const sar_width_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];
12455
+ const sar_height_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1];
12456
+ if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) {
12457
+ sar_width = sar_width_table[aspect_ratio_idc - 1];
12458
+ sar_height = sar_height_table[aspect_ratio_idc - 1];
12459
+ } else if (aspect_ratio_idc === 255) {
12460
+ sar_width = eg.readBits(16);
12461
+ sar_height = eg.readBits(16);
12462
+ }
12463
+ }
12464
+ const overscan_info_present_flag = eg.readBoolean();
12465
+ if (overscan_info_present_flag) {
12466
+ eg.readBoolean();
12467
+ }
12468
+ const video_signal_type_present_flag = eg.readBoolean();
12469
+ if (video_signal_type_present_flag) {
12470
+ eg.readBits(3);
12471
+ eg.readBoolean();
12472
+ const colour_description_present_flag = eg.readBoolean();
12473
+ if (colour_description_present_flag) {
12474
+ eg.readUByte();
12475
+ eg.readUByte();
12476
+ eg.readUByte();
12477
+ }
12478
+ }
12479
+ const chroma_loc_info_present_flag = eg.readBoolean();
12480
+ if (chroma_loc_info_present_flag) {
12481
+ eg.readUEG();
12482
+ eg.readUEG();
12483
+ }
12484
+ eg.readBoolean(); // neutral_chroma_indication_flag
12485
+ eg.readBoolean(); // field_seq_flag
12486
+ eg.readBoolean(); // frame_field_info_present_flag
12487
+ default_display_window_flag = eg.readBoolean();
12488
+ if (default_display_window_flag) {
12489
+ pic_left_offset += eg.readUEG();
12490
+ pic_right_offset += eg.readUEG();
12491
+ pic_top_offset += eg.readUEG();
12492
+ pic_bottom_offset += eg.readUEG();
12493
+ }
12494
+ const vui_timing_info_present_flag = eg.readBoolean();
12495
+ if (vui_timing_info_present_flag) {
12496
+ fps_den = eg.readBits(32);
12497
+ fps_num = eg.readBits(32);
12498
+ const vui_poc_proportional_to_timing_flag = eg.readBoolean();
12499
+ if (vui_poc_proportional_to_timing_flag) {
12500
+ eg.readUEG();
12501
+ }
12502
+ const vui_hrd_parameters_present_flag = eg.readBoolean();
12503
+ if (vui_hrd_parameters_present_flag) {
12504
+ //const commonInfPresentFlag = true;
12505
+ //if (commonInfPresentFlag) {
12506
+ const nal_hrd_parameters_present_flag = eg.readBoolean();
12507
+ const vcl_hrd_parameters_present_flag = eg.readBoolean();
12508
+ let sub_pic_hrd_params_present_flag = false;
12509
+ if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
12510
+ sub_pic_hrd_params_present_flag = eg.readBoolean();
12511
+ if (sub_pic_hrd_params_present_flag) {
12512
+ eg.readUByte();
12513
+ eg.readBits(5);
12514
+ eg.readBoolean();
12515
+ eg.readBits(5);
12516
+ }
12517
+ eg.readBits(4); // bit_rate_scale
12518
+ eg.readBits(4); // cpb_size_scale
12519
+ if (sub_pic_hrd_params_present_flag) {
12520
+ eg.readBits(4);
12521
+ }
12522
+ eg.readBits(5);
12523
+ eg.readBits(5);
12524
+ eg.readBits(5);
12525
+ }
12526
+ //}
12527
+ for (let i = 0; i <= max_sub_layers_minus1; i++) {
12528
+ fps_fixed = eg.readBoolean(); // fixed_pic_rate_general_flag
12529
+ const fixed_pic_rate_within_cvs_flag = fps_fixed || eg.readBoolean();
12530
+ let low_delay_hrd_flag = false;
12531
+ if (fixed_pic_rate_within_cvs_flag) {
12532
+ eg.readEG();
12533
+ } else {
12534
+ low_delay_hrd_flag = eg.readBoolean();
12535
+ }
12536
+ const cpb_cnt = low_delay_hrd_flag ? 1 : eg.readUEG() + 1;
12537
+ if (nal_hrd_parameters_present_flag) {
12538
+ for (let j = 0; j < cpb_cnt; j++) {
12539
+ eg.readUEG();
12540
+ eg.readUEG();
12541
+ if (sub_pic_hrd_params_present_flag) {
12542
+ eg.readUEG();
12543
+ eg.readUEG();
12544
+ }
12545
+ eg.skipBits(1);
12546
+ }
12547
+ }
12548
+ if (vcl_hrd_parameters_present_flag) {
12549
+ for (let j = 0; j < cpb_cnt; j++) {
12550
+ eg.readUEG();
12551
+ eg.readUEG();
12552
+ if (sub_pic_hrd_params_present_flag) {
12553
+ eg.readUEG();
12554
+ eg.readUEG();
12555
+ }
12556
+ eg.skipBits(1);
12557
+ }
12558
+ }
12559
+ }
12560
+ }
11884
12561
  }
12562
+ const bitstream_restriction_flag = eg.readBoolean();
12563
+ if (bitstream_restriction_flag) {
12564
+ eg.readBoolean(); // tiles_fixed_structure_flag
12565
+ eg.readBoolean(); // motion_vectors_over_pic_boundaries_flag
12566
+ eg.readBoolean(); // restricted_ref_pic_lists_flag
12567
+ min_spatial_segmentation_idc = eg.readUEG();
12568
+ }
12569
+ }
12570
+ let width = pic_width_in_luma_samples,
12571
+ height = pic_height_in_luma_samples;
12572
+ if (conformance_window_flag || default_display_window_flag) {
12573
+ let chroma_scale_w = 1,
12574
+ chroma_scale_h = 1;
12575
+ if (chroma_format_idc === 1) {
12576
+ // YUV 420
12577
+ chroma_scale_w = chroma_scale_h = 2;
12578
+ } else if (chroma_format_idc == 2) {
12579
+ // YUV 422
12580
+ chroma_scale_w = 2;
12581
+ }
12582
+ width = pic_width_in_luma_samples - chroma_scale_w * pic_right_offset - chroma_scale_w * pic_left_offset;
12583
+ height = pic_height_in_luma_samples - chroma_scale_h * pic_bottom_offset - chroma_scale_h * pic_top_offset;
12584
+ }
12585
+ const profile_space_string = general_profile_space ? ['A', 'B', 'C'][general_profile_space] : '';
12586
+ const profile_compatibility_buf = general_profile_compatibility_flags_1 << 24 | general_profile_compatibility_flags_2 << 16 | general_profile_compatibility_flags_3 << 8 | general_profile_compatibility_flags_4;
12587
+ let profile_compatibility_rev = 0;
12588
+ for (let i = 0; i < 32; i++) {
12589
+ profile_compatibility_rev = (profile_compatibility_rev | (profile_compatibility_buf >> i & 1) << 31 - i) >>> 0; // reverse bit position (and cast as UInt32)
12590
+ }
12591
+ let profile_compatibility_flags_string = profile_compatibility_rev.toString(16);
12592
+ if (general_profile_idc === 1 && profile_compatibility_flags_string === '2') {
12593
+ profile_compatibility_flags_string = '6';
12594
+ }
12595
+ const tier_flag_string = general_tier_flag ? 'H' : 'L';
12596
+ return {
12597
+ codecString: `hvc1.${profile_space_string}${general_profile_idc}.${profile_compatibility_flags_string}.${tier_flag_string}${general_level_idc}.B0`,
12598
+ params: {
12599
+ general_tier_flag,
12600
+ general_profile_idc,
12601
+ general_profile_space,
12602
+ general_profile_compatibility_flags: [general_profile_compatibility_flags_1, general_profile_compatibility_flags_2, general_profile_compatibility_flags_3, general_profile_compatibility_flags_4],
12603
+ general_constraint_indicator_flags: [general_constraint_indicator_flags_1, general_constraint_indicator_flags_2, general_constraint_indicator_flags_3, general_constraint_indicator_flags_4, general_constraint_indicator_flags_5, general_constraint_indicator_flags_6],
12604
+ general_level_idc,
12605
+ bit_depth: bit_depth_luma_minus8 + 8,
12606
+ bit_depth_luma_minus8,
12607
+ bit_depth_chroma_minus8,
12608
+ min_spatial_segmentation_idc,
12609
+ chroma_format_idc: chroma_format_idc,
12610
+ frame_rate: {
12611
+ fixed: fps_fixed,
12612
+ fps: fps_num / fps_den
12613
+ }
12614
+ },
12615
+ width,
12616
+ height,
12617
+ pixelRatio: [sar_width, sar_height]
12618
+ };
12619
+ }
12620
+ readPPS(pps) {
12621
+ const eg = new ExpGolomb(this.ebsp2rbsp(pps));
12622
+ eg.readUByte();
12623
+ eg.readUByte();
12624
+ eg.skipUEG(); // pic_parameter_set_id
12625
+ eg.skipUEG(); // seq_parameter_set_id
12626
+ eg.skipBits(2); // dependent_slice_segments_enabled_flag, output_flag_present_flag
12627
+ eg.skipBits(3); // num_extra_slice_header_bits
12628
+ eg.skipBits(2); // sign_data_hiding_enabled_flag, cabac_init_present_flag
12629
+ eg.skipUEG();
12630
+ eg.skipUEG();
12631
+ eg.skipEG(); // init_qp_minus26
12632
+ eg.skipBits(2); // constrained_intra_pred_flag, transform_skip_enabled_flag
12633
+ const cu_qp_delta_enabled_flag = eg.readBoolean();
12634
+ if (cu_qp_delta_enabled_flag) {
12635
+ eg.skipUEG();
12636
+ }
12637
+ eg.skipEG(); // cb_qp_offset
12638
+ eg.skipEG(); // cr_qp_offset
12639
+ eg.skipBits(4); // pps_slice_chroma_qp_offsets_present_flag, weighted_pred_flag, weighted_bipred_flag, transquant_bypass_enabled_flag
12640
+ const tiles_enabled_flag = eg.readBoolean();
12641
+ const entropy_coding_sync_enabled_flag = eg.readBoolean();
12642
+ let parallelismType = 1; // slice-based parallel decoding
12643
+ if (entropy_coding_sync_enabled_flag && tiles_enabled_flag) {
12644
+ parallelismType = 0; // mixed-type parallel decoding
12645
+ } else if (entropy_coding_sync_enabled_flag) {
12646
+ parallelismType = 3; // wavefront-based parallel decoding
12647
+ } else if (tiles_enabled_flag) {
12648
+ parallelismType = 2; // tile-based parallel decoding
11885
12649
  }
11886
- track.naluState = state;
11887
- return units;
12650
+ return {
12651
+ parallelismType
12652
+ };
12653
+ }
12654
+ matchSPS(sps1, sps2) {
12655
+ // compare without headers and VPS related params
12656
+ return String.fromCharCode.apply(null, sps1).substr(3) === String.fromCharCode.apply(null, sps2).substr(3);
11888
12657
  }
11889
12658
  }
11890
12659
 
@@ -11902,7 +12671,7 @@ class SampleAesDecrypter {
11902
12671
  });
11903
12672
  }
11904
12673
  decryptBuffer(encryptedData) {
11905
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
12674
+ return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
11906
12675
  }
11907
12676
 
11908
12677
  // AAC - encrypt all full 16 bytes blocks starting from offset 16
@@ -12016,7 +12785,7 @@ class TSDemuxer {
12016
12785
  this.observer = observer;
12017
12786
  this.config = config;
12018
12787
  this.typeSupported = typeSupported;
12019
- this.videoParser = new AvcVideoParser();
12788
+ this.videoParser = null;
12020
12789
  }
12021
12790
  static probe(data) {
12022
12791
  const syncOffset = TSDemuxer.syncOffset(data);
@@ -12181,7 +12950,21 @@ class TSDemuxer {
12181
12950
  case videoPid:
12182
12951
  if (stt) {
12183
12952
  if (videoData && (pes = parsePES(videoData))) {
12184
- this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
12953
+ if (this.videoParser === null) {
12954
+ switch (videoTrack.segmentCodec) {
12955
+ case 'avc':
12956
+ this.videoParser = new AvcVideoParser();
12957
+ break;
12958
+ case 'hevc':
12959
+ {
12960
+ this.videoParser = new HevcVideoParser();
12961
+ }
12962
+ break;
12963
+ }
12964
+ }
12965
+ if (this.videoParser !== null) {
12966
+ this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
12967
+ }
12185
12968
  }
12186
12969
  videoData = {
12187
12970
  data: [],
@@ -12348,8 +13131,22 @@ class TSDemuxer {
12348
13131
  // try to parse last PES packets
12349
13132
  let pes;
12350
13133
  if (videoData && (pes = parsePES(videoData))) {
12351
- this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
12352
- videoTrack.pesData = null;
13134
+ if (this.videoParser === null) {
13135
+ switch (videoTrack.segmentCodec) {
13136
+ case 'avc':
13137
+ this.videoParser = new AvcVideoParser();
13138
+ break;
13139
+ case 'hevc':
13140
+ {
13141
+ this.videoParser = new HevcVideoParser();
13142
+ }
13143
+ break;
13144
+ }
13145
+ }
13146
+ if (this.videoParser !== null) {
13147
+ this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
13148
+ videoTrack.pesData = null;
13149
+ }
12353
13150
  } else {
12354
13151
  // either avcData null or PES truncated, keep it for next frag parsing
12355
13152
  videoTrack.pesData = videoData;
@@ -12682,7 +13479,14 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
12682
13479
  logger.warn('Unsupported EC-3 in M2TS found');
12683
13480
  break;
12684
13481
  case 0x24:
12685
- logger.warn('Unsupported HEVC in M2TS found');
13482
+ // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
13483
+ {
13484
+ if (result.videoPid === -1) {
13485
+ result.videoPid = pid;
13486
+ result.segmentVideoCodec = 'hevc';
13487
+ logger.log('HEVC in M2TS found');
13488
+ }
13489
+ }
12686
13490
  break;
12687
13491
  }
12688
13492
  // move to the next table entry
@@ -12905,6 +13709,8 @@ class MP4 {
12905
13709
  avc1: [],
12906
13710
  // codingname
12907
13711
  avcC: [],
13712
+ hvc1: [],
13713
+ hvcC: [],
12908
13714
  btrt: [],
12909
13715
  dinf: [],
12910
13716
  dref: [],
@@ -13329,8 +14135,10 @@ class MP4 {
13329
14135
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
13330
14136
  }
13331
14137
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
13332
- } else {
14138
+ } else if (track.segmentCodec === 'avc') {
13333
14139
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
14140
+ } else {
14141
+ return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
13334
14142
  }
13335
14143
  }
13336
14144
  static tkhd(track) {
@@ -13468,6 +14276,84 @@ class MP4 {
13468
14276
  const result = appendUint8Array(MP4.FTYP, movie);
13469
14277
  return result;
13470
14278
  }
14279
+ static hvc1(track) {
14280
+ const ps = track.params;
14281
+ const units = [track.vps, track.sps, track.pps];
14282
+ const NALuLengthSize = 4;
14283
+ const config = new Uint8Array([0x01, ps.general_profile_space << 6 | (ps.general_tier_flag ? 32 : 0) | ps.general_profile_idc, ps.general_profile_compatibility_flags[0], ps.general_profile_compatibility_flags[1], ps.general_profile_compatibility_flags[2], ps.general_profile_compatibility_flags[3], ps.general_constraint_indicator_flags[0], ps.general_constraint_indicator_flags[1], ps.general_constraint_indicator_flags[2], ps.general_constraint_indicator_flags[3], ps.general_constraint_indicator_flags[4], ps.general_constraint_indicator_flags[5], ps.general_level_idc, 240 | ps.min_spatial_segmentation_idc >> 8, 255 & ps.min_spatial_segmentation_idc, 252 | ps.parallelismType, 252 | ps.chroma_format_idc, 248 | ps.bit_depth_luma_minus8, 248 | ps.bit_depth_chroma_minus8, 0x00, parseInt(ps.frame_rate.fps), NALuLengthSize - 1 | ps.temporal_id_nested << 2 | ps.num_temporal_layers << 3 | (ps.frame_rate.fixed ? 64 : 0), units.length]);
14284
+
14285
+ // compute hvcC size in bytes
14286
+ let length = config.length;
14287
+ for (let i = 0; i < units.length; i += 1) {
14288
+ length += 3;
14289
+ for (let j = 0; j < units[i].length; j += 1) {
14290
+ length += 2 + units[i][j].length;
14291
+ }
14292
+ }
14293
+ const hvcC = new Uint8Array(length);
14294
+ hvcC.set(config, 0);
14295
+ length = config.length;
14296
+ // append parameter set units: one vps, one or more sps and pps
14297
+ const iMax = units.length - 1;
14298
+ for (let i = 0; i < units.length; i += 1) {
14299
+ hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
14300
+ length += 3;
14301
+ for (let j = 0; j < units[i].length; j += 1) {
14302
+ hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
14303
+ length += 2;
14304
+ hvcC.set(units[i][j], length);
14305
+ length += units[i][j].length;
14306
+ }
14307
+ }
14308
+ const hvcc = MP4.box(MP4.types.hvcC, hvcC);
14309
+ const width = track.width;
14310
+ const height = track.height;
14311
+ const hSpacing = track.pixelRatio[0];
14312
+ const vSpacing = track.pixelRatio[1];
14313
+ return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
14314
+ // reserved
14315
+ 0x00, 0x00, 0x00,
14316
+ // reserved
14317
+ 0x00, 0x01,
14318
+ // data_reference_index
14319
+ 0x00, 0x00,
14320
+ // pre_defined
14321
+ 0x00, 0x00,
14322
+ // reserved
14323
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14324
+ // pre_defined
14325
+ width >> 8 & 0xff, width & 0xff,
14326
+ // width
14327
+ height >> 8 & 0xff, height & 0xff,
14328
+ // height
14329
+ 0x00, 0x48, 0x00, 0x00,
14330
+ // horizresolution
14331
+ 0x00, 0x48, 0x00, 0x00,
14332
+ // vertresolution
14333
+ 0x00, 0x00, 0x00, 0x00,
14334
+ // reserved
14335
+ 0x00, 0x01,
14336
+ // frame_count
14337
+ 0x12, 0x64, 0x61, 0x69, 0x6c,
14338
+ // dailymotion/hls.js
14339
+ 0x79, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x68, 0x6c, 0x73, 0x2e, 0x6a, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14340
+ // compressorname
14341
+ 0x00, 0x18,
14342
+ // depth = 24
14343
+ 0x11, 0x11]),
14344
+ // pre_defined = -1
14345
+ hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
14346
+ // bufferSizeDB
14347
+ 0x00, 0x2d, 0xc6, 0xc0,
14348
+ // maxBitrate
14349
+ 0x00, 0x2d, 0xc6, 0xc0])),
14350
+ // avgBitrate
14351
+ MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
14352
+ // hSpacing
14353
+ hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
14354
+ // vSpacing
14355
+ vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
14356
+ }
13471
14357
  }
13472
14358
  MP4.types = void 0;
13473
14359
  MP4.HDLR_TYPES = void 0;
@@ -13849,9 +14735,9 @@ class MP4Remuxer {
13849
14735
  const foundOverlap = delta < -1;
13850
14736
  if (foundHole || foundOverlap) {
13851
14737
  if (foundHole) {
13852
- logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
14738
+ logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
13853
14739
  } else {
13854
- logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
14740
+ logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
13855
14741
  }
13856
14742
  if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
13857
14743
  firstDTS = nextAvcDts;
@@ -13860,12 +14746,24 @@ class MP4Remuxer {
13860
14746
  inputSamples[0].dts = firstDTS;
13861
14747
  inputSamples[0].pts = firstPTS;
13862
14748
  } else {
14749
+ let isPTSOrderRetained = true;
13863
14750
  for (let i = 0; i < inputSamples.length; i++) {
13864
- if (inputSamples[i].dts > firstPTS) {
14751
+ if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
13865
14752
  break;
13866
14753
  }
14754
+ const prevPTS = inputSamples[i].pts;
13867
14755
  inputSamples[i].dts -= delta;
13868
14756
  inputSamples[i].pts -= delta;
14757
+
14758
+ // check to see if this sample's PTS order has changed
14759
+ // relative to the next one
14760
+ if (i < inputSamples.length - 1) {
14761
+ const nextSamplePTS = inputSamples[i + 1].pts;
14762
+ const currentSamplePTS = inputSamples[i].pts;
14763
+ const currentOrder = nextSamplePTS <= currentSamplePTS;
14764
+ const prevOrder = nextSamplePTS <= prevPTS;
14765
+ isPTSOrderRetained = currentOrder == prevOrder;
14766
+ }
13869
14767
  }
13870
14768
  }
13871
14769
  logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
@@ -14013,7 +14911,7 @@ class MP4Remuxer {
14013
14911
  }
14014
14912
  }
14015
14913
  }
14016
- // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14914
+ // next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14017
14915
  mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
14018
14916
  this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
14019
14917
  this.videoSampleDuration = mp4SampleDuration;
@@ -14146,7 +15044,7 @@ class MP4Remuxer {
14146
15044
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
14147
15045
  for (let j = 0; j < missing; j++) {
14148
15046
  const newStamp = Math.max(nextPts, 0);
14149
- let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
15047
+ let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14150
15048
  if (!fillFrame) {
14151
15049
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
14152
15050
  fillFrame = sample.unit.subarray();
@@ -14274,7 +15172,7 @@ class MP4Remuxer {
14274
15172
  // samples count of this segment's duration
14275
15173
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
14276
15174
  // silent frame
14277
- const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
15175
+ const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14278
15176
  logger.warn('[mp4-remuxer]: remux empty Audio');
14279
15177
  // Can't remux if we can't generate a silent frame...
14280
15178
  if (!silentFrame) {
@@ -14668,13 +15566,15 @@ class Transmuxer {
14668
15566
  initSegmentData
14669
15567
  } = transmuxConfig;
14670
15568
  const keyData = getEncryptionType(uintData, decryptdata);
14671
- if (keyData && keyData.method === 'AES-128') {
15569
+ if (keyData && isFullSegmentEncryption(keyData.method)) {
14672
15570
  const decrypter = this.getDecrypter();
15571
+ const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
15572
+
14673
15573
  // Software decryption is synchronous; webCrypto is not
14674
15574
  if (decrypter.isSync()) {
14675
15575
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
14676
15576
  // data is handled in the flush() call
14677
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
15577
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
14678
15578
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
14679
15579
  const loadingParts = chunkMeta.part > -1;
14680
15580
  if (loadingParts) {
@@ -14686,7 +15586,7 @@ class Transmuxer {
14686
15586
  }
14687
15587
  uintData = new Uint8Array(decryptedData);
14688
15588
  } else {
14689
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
15589
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
14690
15590
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
14691
15591
  // the decrypted data has been transmuxed
14692
15592
  const result = this.push(decryptedData, null, chunkMeta);
@@ -15340,14 +16240,7 @@ class TransmuxerInterface {
15340
16240
  this.observer = new EventEmitter();
15341
16241
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
15342
16242
  this.observer.on(Events.ERROR, forwardMessage);
15343
- const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
15344
- isTypeSupported: () => false
15345
- };
15346
- const m2tsTypeSupported = {
15347
- mpeg: MediaSource.isTypeSupported('audio/mpeg'),
15348
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
15349
- ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
15350
- };
16243
+ const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
15351
16244
 
15352
16245
  // navigator.vendor is not always available in Web Worker
15353
16246
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -15635,7 +16528,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
15635
16528
 
15636
16529
  class AudioStreamController extends BaseStreamController {
15637
16530
  constructor(hls, fragmentTracker, keyLoader) {
15638
- super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);
16531
+ super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
15639
16532
  this.videoBuffer = null;
15640
16533
  this.videoTrackCC = -1;
15641
16534
  this.waitingVideoCC = -1;
@@ -15647,27 +16540,24 @@ class AudioStreamController extends BaseStreamController {
15647
16540
  this.flushing = false;
15648
16541
  this.bufferFlushed = false;
15649
16542
  this.cachedTrackLoadedData = null;
15650
- this._registerListeners();
16543
+ this.registerListeners();
15651
16544
  }
15652
16545
  onHandlerDestroying() {
15653
- this._unregisterListeners();
16546
+ this.unregisterListeners();
15654
16547
  super.onHandlerDestroying();
15655
16548
  this.mainDetails = null;
15656
16549
  this.bufferedTrack = null;
15657
16550
  this.switchingTrack = null;
15658
16551
  }
15659
- _registerListeners() {
16552
+ registerListeners() {
16553
+ super.registerListeners();
15660
16554
  const {
15661
16555
  hls
15662
16556
  } = this;
15663
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
15664
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
15665
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
15666
16557
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
15667
16558
  hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
15668
16559
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
15669
16560
  hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15670
- hls.on(Events.ERROR, this.onError, this);
15671
16561
  hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
15672
16562
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
15673
16563
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -15675,18 +16565,18 @@ class AudioStreamController extends BaseStreamController {
15675
16565
  hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
15676
16566
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
15677
16567
  }
15678
- _unregisterListeners() {
16568
+ unregisterListeners() {
15679
16569
  const {
15680
16570
  hls
15681
16571
  } = this;
15682
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
15683
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
15684
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16572
+ if (!hls) {
16573
+ return;
16574
+ }
16575
+ super.unregisterListeners();
15685
16576
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
15686
16577
  hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
15687
16578
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
15688
16579
  hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
15689
- hls.off(Events.ERROR, this.onError, this);
15690
16580
  hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
15691
16581
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
15692
16582
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -15855,12 +16745,13 @@ class AudioStreamController extends BaseStreamController {
15855
16745
  } = this;
15856
16746
  const config = hls.config;
15857
16747
 
15858
- // 1. if video not attached AND
16748
+ // 1. if buffering is suspended
16749
+ // 2. if video not attached AND
15859
16750
  // start fragment already requested OR start frag prefetch not enabled
15860
- // 2. if tracks or track not loaded and selected
16751
+ // 3. if tracks or track not loaded and selected
15861
16752
  // then exit loop
15862
16753
  // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
15863
- if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
16754
+ if (!this.buffering || !media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
15864
16755
  return;
15865
16756
  }
15866
16757
  const levelInfo = levels[trackId];
@@ -16418,7 +17309,7 @@ class AudioStreamController extends BaseStreamController {
16418
17309
 
16419
17310
  class AudioTrackController extends BasePlaylistController {
16420
17311
  constructor(hls) {
16421
- super(hls, '[audio-track-controller]');
17312
+ super(hls, 'audio-track-controller');
16422
17313
  this.tracks = [];
16423
17314
  this.groupIds = null;
16424
17315
  this.tracksInGroup = [];
@@ -16737,26 +17628,23 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
16737
17628
 
16738
17629
  class SubtitleStreamController extends BaseStreamController {
16739
17630
  constructor(hls, fragmentTracker, keyLoader) {
16740
- super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);
17631
+ super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
16741
17632
  this.currentTrackId = -1;
16742
17633
  this.tracksBuffered = [];
16743
17634
  this.mainDetails = null;
16744
- this._registerListeners();
17635
+ this.registerListeners();
16745
17636
  }
16746
17637
  onHandlerDestroying() {
16747
- this._unregisterListeners();
17638
+ this.unregisterListeners();
16748
17639
  super.onHandlerDestroying();
16749
17640
  this.mainDetails = null;
16750
17641
  }
16751
- _registerListeners() {
17642
+ registerListeners() {
17643
+ super.registerListeners();
16752
17644
  const {
16753
17645
  hls
16754
17646
  } = this;
16755
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
16756
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
16757
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16758
17647
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16759
- hls.on(Events.ERROR, this.onError, this);
16760
17648
  hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
16761
17649
  hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
16762
17650
  hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -16764,15 +17652,12 @@ class SubtitleStreamController extends BaseStreamController {
16764
17652
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
16765
17653
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
16766
17654
  }
16767
- _unregisterListeners() {
17655
+ unregisterListeners() {
17656
+ super.unregisterListeners();
16768
17657
  const {
16769
17658
  hls
16770
17659
  } = this;
16771
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
16772
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
16773
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16774
17660
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16775
- hls.off(Events.ERROR, this.onError, this);
16776
17661
  hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
16777
17662
  hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
16778
17663
  hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
@@ -16999,10 +17884,10 @@ class SubtitleStreamController extends BaseStreamController {
16999
17884
  return;
17000
17885
  }
17001
17886
  // check to see if the payload needs to be decrypted
17002
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
17887
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
17003
17888
  const startTime = performance.now();
17004
17889
  // decrypt the subtitles
17005
- this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
17890
+ this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
17006
17891
  hls.trigger(Events.ERROR, {
17007
17892
  type: ErrorTypes.MEDIA_ERROR,
17008
17893
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -17136,7 +18021,7 @@ class BufferableInstance {
17136
18021
 
17137
18022
  class SubtitleTrackController extends BasePlaylistController {
17138
18023
  constructor(hls) {
17139
- super(hls, '[subtitle-track-controller]');
18024
+ super(hls, 'subtitle-track-controller');
17140
18025
  this.media = null;
17141
18026
  this.tracks = [];
17142
18027
  this.groupIds = null;
@@ -17145,10 +18030,10 @@ class SubtitleTrackController extends BasePlaylistController {
17145
18030
  this.currentTrack = null;
17146
18031
  this.selectDefaultTrack = true;
17147
18032
  this.queuedDefaultTrack = -1;
17148
- this.asyncPollTrackChange = () => this.pollTrackChange(0);
17149
18033
  this.useTextTrackPolling = false;
17150
18034
  this.subtitlePollingInterval = -1;
17151
18035
  this._subtitleDisplay = true;
18036
+ this.asyncPollTrackChange = () => this.pollTrackChange(0);
17152
18037
  this.onTextTracksChanged = () => {
17153
18038
  if (!this.useTextTrackPolling) {
17154
18039
  self.clearInterval(this.subtitlePollingInterval);
@@ -17182,6 +18067,7 @@ class SubtitleTrackController extends BasePlaylistController {
17182
18067
  this.tracks.length = 0;
17183
18068
  this.tracksInGroup.length = 0;
17184
18069
  this.currentTrack = null;
18070
+ // @ts-ignore
17185
18071
  this.onTextTracksChanged = this.asyncPollTrackChange = null;
17186
18072
  super.destroy();
17187
18073
  }
@@ -17642,8 +18528,9 @@ class BufferOperationQueue {
17642
18528
  }
17643
18529
 
17644
18530
  const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
17645
- class BufferController {
18531
+ class BufferController extends Logger {
17646
18532
  constructor(hls) {
18533
+ super('buffer-controller', hls.logger);
17647
18534
  // The level details used to determine duration, target-duration and live
17648
18535
  this.details = null;
17649
18536
  // cache the self generated object url to detect hijack of video tag
@@ -17673,9 +18560,6 @@ class BufferController {
17673
18560
  this.tracks = {};
17674
18561
  this.pendingTracks = {};
17675
18562
  this.sourceBuffer = void 0;
17676
- this.log = void 0;
17677
- this.warn = void 0;
17678
- this.error = void 0;
17679
18563
  this._onEndStreaming = event => {
17680
18564
  if (!this.hls) {
17681
18565
  return;
@@ -17721,15 +18605,11 @@ class BufferController {
17721
18605
  _objectUrl
17722
18606
  } = this;
17723
18607
  if (mediaSrc !== _objectUrl) {
17724
- logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
18608
+ this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
17725
18609
  }
17726
18610
  };
17727
18611
  this.hls = hls;
17728
- const logPrefix = '[buffer-controller]';
17729
18612
  this.appendSource = hls.config.preferManagedMediaSource;
17730
- this.log = logger.log.bind(logger, logPrefix);
17731
- this.warn = logger.warn.bind(logger, logPrefix);
17732
- this.error = logger.error.bind(logger, logPrefix);
17733
18613
  this._initSourceBuffer();
17734
18614
  this.registerListeners();
17735
18615
  }
@@ -17742,6 +18622,12 @@ class BufferController {
17742
18622
  this.lastMpegAudioChunk = null;
17743
18623
  // @ts-ignore
17744
18624
  this.hls = null;
18625
+ // @ts-ignore
18626
+ this._onMediaSourceOpen = this._onMediaSourceClose = null;
18627
+ // @ts-ignore
18628
+ this._onMediaSourceEnded = null;
18629
+ // @ts-ignore
18630
+ this._onStartStreaming = this._onEndStreaming = null;
17745
18631
  }
17746
18632
  registerListeners() {
17747
18633
  const {
@@ -17904,6 +18790,7 @@ class BufferController {
17904
18790
  this.resetBuffer(type);
17905
18791
  });
17906
18792
  this._initSourceBuffer();
18793
+ this.hls.resumeBuffering();
17907
18794
  }
17908
18795
  resetBuffer(type) {
17909
18796
  const sb = this.sourceBuffer[type];
@@ -21006,14 +21893,12 @@ class TimelineController {
21006
21893
  this.cea608Parser1 = this.cea608Parser2 = undefined;
21007
21894
  }
21008
21895
  initCea608Parsers() {
21009
- if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) {
21010
- const channel1 = new OutputFilter(this, 'textTrack1');
21011
- const channel2 = new OutputFilter(this, 'textTrack2');
21012
- const channel3 = new OutputFilter(this, 'textTrack3');
21013
- const channel4 = new OutputFilter(this, 'textTrack4');
21014
- this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
21015
- this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
21016
- }
21896
+ const channel1 = new OutputFilter(this, 'textTrack1');
21897
+ const channel2 = new OutputFilter(this, 'textTrack2');
21898
+ const channel3 = new OutputFilter(this, 'textTrack3');
21899
+ const channel4 = new OutputFilter(this, 'textTrack4');
21900
+ this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
21901
+ this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
21017
21902
  }
21018
21903
  addCues(trackName, startTime, endTime, screen, cueRanges) {
21019
21904
  // skip cues which overlap more than 50% with previously parsed time ranges
@@ -21251,7 +22136,7 @@ class TimelineController {
21251
22136
  if (inUseTracks != null && inUseTracks.length) {
21252
22137
  const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
21253
22138
  if (unusedTextTracks.length) {
21254
- logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
22139
+ this.hls.logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
21255
22140
  }
21256
22141
  }
21257
22142
  } else if (this.tracks.length) {
@@ -21296,26 +22181,23 @@ class TimelineController {
21296
22181
  return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
21297
22182
  }
21298
22183
  onFragLoading(event, data) {
21299
- this.initCea608Parsers();
21300
- const {
21301
- cea608Parser1,
21302
- cea608Parser2,
21303
- lastCc,
21304
- lastSn,
21305
- lastPartIndex
21306
- } = this;
21307
- if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
21308
- return;
21309
- }
21310
22184
  // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
21311
- if (data.frag.type === PlaylistLevelType.MAIN) {
22185
+ if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
21312
22186
  var _data$part$index, _data$part;
22187
+ const {
22188
+ cea608Parser1,
22189
+ cea608Parser2,
22190
+ lastSn
22191
+ } = this;
22192
+ if (!cea608Parser1 || !cea608Parser2) {
22193
+ return;
22194
+ }
21313
22195
  const {
21314
22196
  cc,
21315
22197
  sn
21316
22198
  } = data.frag;
21317
- const partIndex = (_data$part$index = data == null ? void 0 : (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
21318
- if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
22199
+ const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
22200
+ if (!(sn === lastSn + 1 || sn === lastSn && partIndex === this.lastPartIndex + 1 || cc === this.lastCc)) {
21319
22201
  cea608Parser1.reset();
21320
22202
  cea608Parser2.reset();
21321
22203
  }
@@ -21372,7 +22254,7 @@ class TimelineController {
21372
22254
  frag: frag
21373
22255
  });
21374
22256
  }, error => {
21375
- logger.log(`Failed to parse IMSC1: ${error}`);
22257
+ hls.logger.log(`Failed to parse IMSC1: ${error}`);
21376
22258
  hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
21377
22259
  success: false,
21378
22260
  frag: frag,
@@ -21413,7 +22295,7 @@ class TimelineController {
21413
22295
  this._fallbackToIMSC1(frag, payload);
21414
22296
  }
21415
22297
  // Something went wrong while parsing. Trigger event with success false.
21416
- logger.log(`Failed to parse VTT cue: ${error}`);
22298
+ hls.logger.log(`Failed to parse VTT cue: ${error}`);
21417
22299
  if (missingInitPTS && maxAvCC > frag.cc) {
21418
22300
  return;
21419
22301
  }
@@ -21474,12 +22356,7 @@ class TimelineController {
21474
22356
  this.captionsTracks = {};
21475
22357
  }
21476
22358
  onFragParsingUserdata(event, data) {
21477
- this.initCea608Parsers();
21478
- const {
21479
- cea608Parser1,
21480
- cea608Parser2
21481
- } = this;
21482
- if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
22359
+ if (!this.enabled || !this.config.enableCEA708Captions) {
21483
22360
  return;
21484
22361
  }
21485
22362
  const {
@@ -21494,9 +22371,12 @@ class TimelineController {
21494
22371
  for (let i = 0; i < samples.length; i++) {
21495
22372
  const ccBytes = samples[i].bytes;
21496
22373
  if (ccBytes) {
22374
+ if (!this.cea608Parser1) {
22375
+ this.initCea608Parsers();
22376
+ }
21497
22377
  const ccdatas = this.extractCea608Data(ccBytes);
21498
- cea608Parser1.addData(samples[i].pts, ccdatas[0]);
21499
- cea608Parser2.addData(samples[i].pts, ccdatas[1]);
22378
+ this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
22379
+ this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
21500
22380
  }
21501
22381
  }
21502
22382
  }
@@ -21692,7 +22572,7 @@ class CapLevelController {
21692
22572
  const hls = this.hls;
21693
22573
  const maxLevel = this.getMaxLevel(levels.length - 1);
21694
22574
  if (maxLevel !== this.autoLevelCapping) {
21695
- logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
22575
+ hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
21696
22576
  }
21697
22577
  hls.autoLevelCapping = maxLevel;
21698
22578
  if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
@@ -21870,10 +22750,10 @@ class FPSController {
21870
22750
  totalDroppedFrames: droppedFrames
21871
22751
  });
21872
22752
  if (droppedFPS > 0) {
21873
- // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
22753
+ // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
21874
22754
  if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
21875
22755
  let currentLevel = hls.currentLevel;
21876
- logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
22756
+ hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
21877
22757
  if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
21878
22758
  currentLevel = currentLevel - 1;
21879
22759
  hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
@@ -21905,7 +22785,6 @@ class FPSController {
21905
22785
  }
21906
22786
  }
21907
22787
 
21908
- const LOGGER_PREFIX = '[eme]';
21909
22788
  /**
21910
22789
  * Controller to deal with encrypted media extensions (EME)
21911
22790
  * @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
@@ -21913,8 +22792,9 @@ const LOGGER_PREFIX = '[eme]';
21913
22792
  * @class
21914
22793
  * @constructor
21915
22794
  */
21916
- class EMEController {
22795
+ class EMEController extends Logger {
21917
22796
  constructor(hls) {
22797
+ super('eme', hls.logger);
21918
22798
  this.hls = void 0;
21919
22799
  this.config = void 0;
21920
22800
  this.media = null;
@@ -21924,12 +22804,100 @@ class EMEController {
21924
22804
  this.mediaKeySessions = [];
21925
22805
  this.keyIdToKeySessionPromise = {};
21926
22806
  this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
21927
- this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
21928
- this.onWaitingForKey = this._onWaitingForKey.bind(this);
21929
- this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
21930
- this.log = logger.log.bind(logger, LOGGER_PREFIX);
21931
- this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
21932
- this.error = logger.error.bind(logger, LOGGER_PREFIX);
22807
+ this.onMediaEncrypted = event => {
22808
+ const {
22809
+ initDataType,
22810
+ initData
22811
+ } = event;
22812
+ this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22813
+
22814
+ // Ignore event when initData is null
22815
+ if (initData === null) {
22816
+ return;
22817
+ }
22818
+ let keyId;
22819
+ let keySystemDomain;
22820
+ if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22821
+ // Match sinf keyId to playlist skd://keyId=
22822
+ const json = bin2str(new Uint8Array(initData));
22823
+ try {
22824
+ const sinf = base64Decode(JSON.parse(json).sinf);
22825
+ const tenc = parseSinf(new Uint8Array(sinf));
22826
+ if (!tenc) {
22827
+ return;
22828
+ }
22829
+ keyId = tenc.subarray(8, 24);
22830
+ keySystemDomain = KeySystems.FAIRPLAY;
22831
+ } catch (error) {
22832
+ this.warn('Failed to parse sinf "encrypted" event message initData');
22833
+ return;
22834
+ }
22835
+ } else {
22836
+ // Support clear-lead key-session creation (otherwise depend on playlist keys)
22837
+ const psshInfo = parsePssh(initData);
22838
+ if (psshInfo === null) {
22839
+ return;
22840
+ }
22841
+ if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22842
+ keyId = psshInfo.data.subarray(8, 24);
22843
+ }
22844
+ keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22845
+ }
22846
+ if (!keySystemDomain || !keyId) {
22847
+ return;
22848
+ }
22849
+ const keyIdHex = Hex.hexDump(keyId);
22850
+ const {
22851
+ keyIdToKeySessionPromise,
22852
+ mediaKeySessions
22853
+ } = this;
22854
+ let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22855
+ for (let i = 0; i < mediaKeySessions.length; i++) {
22856
+ // Match playlist key
22857
+ const keyContext = mediaKeySessions[i];
22858
+ const decryptdata = keyContext.decryptdata;
22859
+ if (decryptdata.pssh || !decryptdata.keyId) {
22860
+ continue;
22861
+ }
22862
+ const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22863
+ if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22864
+ keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22865
+ delete keyIdToKeySessionPromise[oldKeyIdHex];
22866
+ decryptdata.pssh = new Uint8Array(initData);
22867
+ decryptdata.keyId = keyId;
22868
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22869
+ return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22870
+ });
22871
+ break;
22872
+ }
22873
+ }
22874
+ if (!keySessionContextPromise) {
22875
+ // Clear-lead key (not encountered in playlist)
22876
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22877
+ keySystem,
22878
+ mediaKeys
22879
+ }) => {
22880
+ var _keySystemToKeySystem;
22881
+ this.throwIfDestroyed();
22882
+ const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22883
+ decryptdata.pssh = new Uint8Array(initData);
22884
+ decryptdata.keyId = keyId;
22885
+ return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22886
+ this.throwIfDestroyed();
22887
+ const keySessionContext = this.createMediaKeySessionContext({
22888
+ decryptdata,
22889
+ keySystem,
22890
+ mediaKeys
22891
+ });
22892
+ return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22893
+ });
22894
+ });
22895
+ }
22896
+ keySessionContextPromise.catch(error => this.handleError(error));
22897
+ };
22898
+ this.onWaitingForKey = event => {
22899
+ this.log(`"${event.type}" event`);
22900
+ };
21933
22901
  this.hls = hls;
21934
22902
  this.config = hls.config;
21935
22903
  this.registerListeners();
@@ -21943,9 +22911,9 @@ class EMEController {
21943
22911
  config.licenseXhrSetup = config.licenseResponseCallback = undefined;
21944
22912
  config.drmSystems = config.drmSystemOptions = {};
21945
22913
  // @ts-ignore
21946
- this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
22914
+ this.hls = this.config = this.keyIdToKeySessionPromise = null;
21947
22915
  // @ts-ignore
21948
- this.config = null;
22916
+ this.onMediaEncrypted = this.onWaitingForKey = null;
21949
22917
  }
21950
22918
  registerListeners() {
21951
22919
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -22209,100 +23177,6 @@ class EMEController {
22209
23177
  }
22210
23178
  return this.attemptKeySystemAccess(keySystemsToAttempt);
22211
23179
  }
22212
- _onMediaEncrypted(event) {
22213
- const {
22214
- initDataType,
22215
- initData
22216
- } = event;
22217
- this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22218
-
22219
- // Ignore event when initData is null
22220
- if (initData === null) {
22221
- return;
22222
- }
22223
- let keyId;
22224
- let keySystemDomain;
22225
- if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22226
- // Match sinf keyId to playlist skd://keyId=
22227
- const json = bin2str(new Uint8Array(initData));
22228
- try {
22229
- const sinf = base64Decode(JSON.parse(json).sinf);
22230
- const tenc = parseSinf(new Uint8Array(sinf));
22231
- if (!tenc) {
22232
- return;
22233
- }
22234
- keyId = tenc.subarray(8, 24);
22235
- keySystemDomain = KeySystems.FAIRPLAY;
22236
- } catch (error) {
22237
- this.warn('Failed to parse sinf "encrypted" event message initData');
22238
- return;
22239
- }
22240
- } else {
22241
- // Support clear-lead key-session creation (otherwise depend on playlist keys)
22242
- const psshInfo = parsePssh(initData);
22243
- if (psshInfo === null) {
22244
- return;
22245
- }
22246
- if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22247
- keyId = psshInfo.data.subarray(8, 24);
22248
- }
22249
- keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22250
- }
22251
- if (!keySystemDomain || !keyId) {
22252
- return;
22253
- }
22254
- const keyIdHex = Hex.hexDump(keyId);
22255
- const {
22256
- keyIdToKeySessionPromise,
22257
- mediaKeySessions
22258
- } = this;
22259
- let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22260
- for (let i = 0; i < mediaKeySessions.length; i++) {
22261
- // Match playlist key
22262
- const keyContext = mediaKeySessions[i];
22263
- const decryptdata = keyContext.decryptdata;
22264
- if (decryptdata.pssh || !decryptdata.keyId) {
22265
- continue;
22266
- }
22267
- const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22268
- if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22269
- keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22270
- delete keyIdToKeySessionPromise[oldKeyIdHex];
22271
- decryptdata.pssh = new Uint8Array(initData);
22272
- decryptdata.keyId = keyId;
22273
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22274
- return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22275
- });
22276
- break;
22277
- }
22278
- }
22279
- if (!keySessionContextPromise) {
22280
- // Clear-lead key (not encountered in playlist)
22281
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22282
- keySystem,
22283
- mediaKeys
22284
- }) => {
22285
- var _keySystemToKeySystem;
22286
- this.throwIfDestroyed();
22287
- const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22288
- decryptdata.pssh = new Uint8Array(initData);
22289
- decryptdata.keyId = keyId;
22290
- return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22291
- this.throwIfDestroyed();
22292
- const keySessionContext = this.createMediaKeySessionContext({
22293
- decryptdata,
22294
- keySystem,
22295
- mediaKeys
22296
- });
22297
- return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22298
- });
22299
- });
22300
- }
22301
- keySessionContextPromise.catch(error => this.handleError(error));
22302
- }
22303
- _onWaitingForKey(event) {
22304
- this.log(`"${event.type}" event`);
22305
- }
22306
23180
  attemptSetMediaKeys(keySystem, mediaKeys) {
22307
23181
  const queue = this.setMediaKeysQueue.slice();
22308
23182
  this.log(`Setting media-keys for "${keySystem}"`);
@@ -22895,20 +23769,6 @@ class SfItem {
22895
23769
  }
22896
23770
  }
22897
23771
 
22898
- /**
22899
- * A class to represent structured field tokens when `Symbol` is not available.
22900
- *
22901
- * @group Structured Field
22902
- *
22903
- * @beta
22904
- */
22905
- class SfToken {
22906
- constructor(description) {
22907
- this.description = void 0;
22908
- this.description = description;
22909
- }
22910
- }
22911
-
22912
23772
  const DICT = 'Dict';
22913
23773
 
22914
23774
  function format(value) {
@@ -22932,29 +23792,27 @@ function throwError(action, src, type, cause) {
22932
23792
  });
22933
23793
  }
22934
23794
 
22935
- const BARE_ITEM = 'Bare Item';
22936
-
22937
- const BOOLEAN = 'Boolean';
22938
-
22939
- const BYTES = 'Byte Sequence';
22940
-
22941
- const DECIMAL = 'Decimal';
22942
-
22943
- const INTEGER = 'Integer';
22944
-
22945
- function isInvalidInt(value) {
22946
- return value < -999999999999999 || 999999999999999 < value;
23795
+ function serializeError(src, type, cause) {
23796
+ return throwError('serialize', src, type, cause);
22947
23797
  }
22948
23798
 
22949
- const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
22950
-
22951
- const TOKEN = 'Token';
23799
+ /**
23800
+ * A class to represent structured field tokens when `Symbol` is not available.
23801
+ *
23802
+ * @group Structured Field
23803
+ *
23804
+ * @beta
23805
+ */
23806
+ class SfToken {
23807
+ constructor(description) {
23808
+ this.description = void 0;
23809
+ this.description = description;
23810
+ }
23811
+ }
22952
23812
 
22953
- const KEY = 'Key';
23813
+ const BARE_ITEM = 'Bare Item';
22954
23814
 
22955
- function serializeError(src, type, cause) {
22956
- return throwError('serialize', src, type, cause);
22957
- }
23815
+ const BOOLEAN = 'Boolean';
22958
23816
 
22959
23817
  // 4.1.9. Serializing a Boolean
22960
23818
  //
@@ -22993,6 +23851,8 @@ function base64encode(binary) {
22993
23851
  return btoa(String.fromCharCode(...binary));
22994
23852
  }
22995
23853
 
23854
+ const BYTES = 'Byte Sequence';
23855
+
22996
23856
  // 4.1.8. Serializing a Byte Sequence
22997
23857
  //
22998
23858
  // Given a Byte Sequence as input_bytes, return an ASCII string suitable
@@ -23024,6 +23884,12 @@ function serializeByteSequence(value) {
23024
23884
  return `:${base64encode(value)}:`;
23025
23885
  }
23026
23886
 
23887
+ const INTEGER = 'Integer';
23888
+
23889
+ function isInvalidInt(value) {
23890
+ return value < -999999999999999 || 999999999999999 < value;
23891
+ }
23892
+
23027
23893
  // 4.1.4. Serializing an Integer
23028
23894
  //
23029
23895
  // Given an Integer as input_integer, return an ASCII string suitable
@@ -23089,6 +23955,8 @@ function roundToEven(value, precision) {
23089
23955
  }
23090
23956
  }
23091
23957
 
23958
+ const DECIMAL = 'Decimal';
23959
+
23092
23960
  // 4.1.5. Serializing a Decimal
23093
23961
  //
23094
23962
  // Given a decimal number as input_decimal, return an ASCII string
@@ -23134,6 +24002,8 @@ function serializeDecimal(value) {
23134
24002
 
23135
24003
  const STRING = 'String';
23136
24004
 
24005
+ const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
24006
+
23137
24007
  // 4.1.6. Serializing a String
23138
24008
  //
23139
24009
  // Given a String as input_string, return an ASCII string suitable for
@@ -23169,6 +24039,8 @@ function symbolToStr(symbol) {
23169
24039
  return symbol.description || symbol.toString().slice(7, -1);
23170
24040
  }
23171
24041
 
24042
+ const TOKEN = 'Token';
24043
+
23172
24044
  function serializeToken(token) {
23173
24045
  const value = symbolToStr(token);
23174
24046
  if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
@@ -23236,6 +24108,8 @@ function serializeBareItem(value) {
23236
24108
  }
23237
24109
  }
23238
24110
 
24111
+ const KEY = 'Key';
24112
+
23239
24113
  // 4.1.1.3. Serializing a Key
23240
24114
  //
23241
24115
  // Given a key as input_key, return an ASCII string suitable for use in
@@ -23477,36 +24351,6 @@ function urlToRelativePath(url, base) {
23477
24351
  return toPath.join('/');
23478
24352
  }
23479
24353
 
23480
- /**
23481
- * Generate a random v4 UUID
23482
- *
23483
- * @returns A random v4 UUID
23484
- *
23485
- * @group Utils
23486
- *
23487
- * @beta
23488
- */
23489
- function uuid() {
23490
- try {
23491
- return crypto.randomUUID();
23492
- } catch (error) {
23493
- try {
23494
- const url = URL.createObjectURL(new Blob());
23495
- const uuid = url.toString();
23496
- URL.revokeObjectURL(url);
23497
- return uuid.slice(uuid.lastIndexOf('/') + 1);
23498
- } catch (error) {
23499
- let dt = new Date().getTime();
23500
- const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
23501
- const r = (dt + Math.random() * 16) % 16 | 0;
23502
- dt = Math.floor(dt / 16);
23503
- return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
23504
- });
23505
- return uuid;
23506
- }
23507
- }
23508
- }
23509
-
23510
24354
  const toRounded = value => Math.round(value);
23511
24355
  const toUrlSafe = (value, options) => {
23512
24356
  if (options != null && options.baseUrl) {
@@ -23732,6 +24576,36 @@ function appendCmcdQuery(url, cmcd, options) {
23732
24576
  return `${url}${separator}${query}`;
23733
24577
  }
23734
24578
 
24579
+ /**
24580
+ * Generate a random v4 UUID
24581
+ *
24582
+ * @returns A random v4 UUID
24583
+ *
24584
+ * @group Utils
24585
+ *
24586
+ * @beta
24587
+ */
24588
+ function uuid() {
24589
+ try {
24590
+ return crypto.randomUUID();
24591
+ } catch (error) {
24592
+ try {
24593
+ const url = URL.createObjectURL(new Blob());
24594
+ const uuid = url.toString();
24595
+ URL.revokeObjectURL(url);
24596
+ return uuid.slice(uuid.lastIndexOf('/') + 1);
24597
+ } catch (error) {
24598
+ let dt = new Date().getTime();
24599
+ const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
24600
+ const r = (dt + Math.random() * 16) % 16 | 0;
24601
+ dt = Math.floor(dt / 16);
24602
+ return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
24603
+ });
24604
+ return uuid;
24605
+ }
24606
+ }
24607
+ }
24608
+
23735
24609
  /**
23736
24610
  * Controller to deal with Common Media Client Data (CMCD)
23737
24611
  * @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
@@ -23775,7 +24649,7 @@ class CMCDController {
23775
24649
  su: !this.initialized
23776
24650
  });
23777
24651
  } catch (error) {
23778
- logger.warn('Could not generate manifest CMCD data.', error);
24652
+ this.hls.logger.warn('Could not generate manifest CMCD data.', error);
23779
24653
  }
23780
24654
  };
23781
24655
  /**
@@ -23795,9 +24669,15 @@ class CMCDController {
23795
24669
  data.tb = this.getTopBandwidth(ot) / 1000;
23796
24670
  data.bl = this.getBufferLength(ot);
23797
24671
  }
24672
+ const next = this.getNextFrag(fragment);
24673
+ if (next) {
24674
+ if (next.url && next.url !== fragment.url) {
24675
+ data.nor = next.url;
24676
+ }
24677
+ }
23798
24678
  this.apply(context, data);
23799
24679
  } catch (error) {
23800
- logger.warn('Could not generate segment CMCD data.', error);
24680
+ this.hls.logger.warn('Could not generate segment CMCD data.', error);
23801
24681
  }
23802
24682
  };
23803
24683
  this.hls = hls;
@@ -23887,7 +24767,7 @@ class CMCDController {
23887
24767
  data.su = this.buffering;
23888
24768
  }
23889
24769
 
23890
- // TODO: Implement rtp, nrr, nor, dl
24770
+ // TODO: Implement rtp, nrr, dl
23891
24771
 
23892
24772
  const {
23893
24773
  includeKeys
@@ -23898,15 +24778,28 @@ class CMCDController {
23898
24778
  return acc;
23899
24779
  }, {});
23900
24780
  }
24781
+ const options = {
24782
+ baseUrl: context.url
24783
+ };
23901
24784
  if (this.useHeaders) {
23902
24785
  if (!context.headers) {
23903
24786
  context.headers = {};
23904
24787
  }
23905
- appendCmcdHeaders(context.headers, data);
24788
+ appendCmcdHeaders(context.headers, data, options);
23906
24789
  } else {
23907
- context.url = appendCmcdQuery(context.url, data);
24790
+ context.url = appendCmcdQuery(context.url, data, options);
24791
+ }
24792
+ }
24793
+ getNextFrag(fragment) {
24794
+ var _this$hls$levels$frag;
24795
+ const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
24796
+ if (levelDetails) {
24797
+ const index = fragment.sn - levelDetails.startSN;
24798
+ return levelDetails.fragments[index + 1];
23908
24799
  }
24800
+ return undefined;
23909
24801
  }
24802
+
23910
24803
  /**
23911
24804
  * The CMCD object type.
23912
24805
  */
@@ -24035,10 +24928,10 @@ class CMCDController {
24035
24928
  }
24036
24929
 
24037
24930
  const PATHWAY_PENALTY_DURATION_MS = 300000;
24038
- class ContentSteeringController {
24931
+ class ContentSteeringController extends Logger {
24039
24932
  constructor(hls) {
24933
+ super('content-steering', hls.logger);
24040
24934
  this.hls = void 0;
24041
- this.log = void 0;
24042
24935
  this.loader = null;
24043
24936
  this.uri = null;
24044
24937
  this.pathwayId = '.';
@@ -24053,7 +24946,6 @@ class ContentSteeringController {
24053
24946
  this.subtitleTracks = null;
24054
24947
  this.penalizedPathways = {};
24055
24948
  this.hls = hls;
24056
- this.log = logger.log.bind(logger, `[content-steering]:`);
24057
24949
  this.registerListeners();
24058
24950
  }
24059
24951
  registerListeners() {
@@ -24177,7 +25069,7 @@ class ContentSteeringController {
24177
25069
  errorAction.resolved = this.pathwayId !== errorPathway;
24178
25070
  }
24179
25071
  if (!errorAction.resolved) {
24180
- 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)}`);
25072
+ this.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
24181
25073
  }
24182
25074
  }
24183
25075
  }
@@ -24348,7 +25240,7 @@ class ContentSteeringController {
24348
25240
  onSuccess: (response, stats, context, networkDetails) => {
24349
25241
  this.log(`Loaded steering manifest: "${url}"`);
24350
25242
  const steeringData = response.data;
24351
- if (steeringData.VERSION !== 1) {
25243
+ if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
24352
25244
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
24353
25245
  return;
24354
25246
  }
@@ -25318,7 +26210,7 @@ function timelineConfig() {
25318
26210
  /**
25319
26211
  * @ignore
25320
26212
  */
25321
- function mergeConfig(defaultConfig, userConfig) {
26213
+ function mergeConfig(defaultConfig, userConfig, logger) {
25322
26214
  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
25323
26215
  throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
25324
26216
  }
@@ -25388,7 +26280,7 @@ function deepCpy(obj) {
25388
26280
  /**
25389
26281
  * @ignore
25390
26282
  */
25391
- function enableStreamingMode(config) {
26283
+ function enableStreamingMode(config, logger) {
25392
26284
  const currentLoader = config.loader;
25393
26285
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
25394
26286
  // If a developer has configured their own loader, respect that choice
@@ -25405,10 +26297,9 @@ function enableStreamingMode(config) {
25405
26297
  }
25406
26298
  }
25407
26299
 
25408
- let chromeOrFirefox;
25409
26300
  class LevelController extends BasePlaylistController {
25410
26301
  constructor(hls, contentSteeringController) {
25411
- super(hls, '[level-controller]');
26302
+ super(hls, 'level-controller');
25412
26303
  this._levels = [];
25413
26304
  this._firstLevel = -1;
25414
26305
  this._maxAutoLevel = -1;
@@ -25479,23 +26370,15 @@ class LevelController extends BasePlaylistController {
25479
26370
  let videoCodecFound = false;
25480
26371
  let audioCodecFound = false;
25481
26372
  data.levels.forEach(levelParsed => {
25482
- var _audioCodec, _videoCodec;
26373
+ var _videoCodec;
25483
26374
  const attributes = levelParsed.attrs;
25484
-
25485
- // erase audio codec info if browser does not support mp4a.40.34.
25486
- // demuxer will autodetect codec and fallback to mpeg/audio
25487
26375
  let {
25488
26376
  audioCodec,
25489
26377
  videoCodec
25490
26378
  } = levelParsed;
25491
- if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
25492
- chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
25493
- if (chromeOrFirefox) {
25494
- levelParsed.audioCodec = audioCodec = undefined;
25495
- }
25496
- }
25497
26379
  if (audioCodec) {
25498
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
26380
+ // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
26381
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
25499
26382
  }
25500
26383
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
25501
26384
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -25837,7 +26720,12 @@ class LevelController extends BasePlaylistController {
25837
26720
  if (curLevel.fragmentError === 0) {
25838
26721
  curLevel.loadError = 0;
25839
26722
  }
25840
- this.playlistLoaded(level, data, curLevel.details);
26723
+ // Ignore matching details populated by loading a Media Playlist directly
26724
+ let previousDetails = curLevel.details;
26725
+ if (previousDetails === data.details && previousDetails.advanced) {
26726
+ previousDetails = undefined;
26727
+ }
26728
+ this.playlistLoaded(level, data, previousDetails);
25841
26729
  } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
25842
26730
  // received a delta playlist update that cannot be merged
25843
26731
  details.deltaUpdateFailed = true;
@@ -26081,6 +26969,8 @@ class KeyLoader {
26081
26969
  }
26082
26970
  return this.loadKeyEME(keyInfo, frag);
26083
26971
  case 'AES-128':
26972
+ case 'AES-256':
26973
+ case 'AES-256-CTR':
26084
26974
  return this.loadKeyHTTP(keyInfo, frag);
26085
26975
  default:
26086
26976
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -26218,8 +27108,9 @@ const STALL_MINIMUM_DURATION_MS = 250;
26218
27108
  const MAX_START_GAP_JUMP = 2.0;
26219
27109
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
26220
27110
  const SKIP_BUFFER_RANGE_START = 0.05;
26221
- class GapController {
27111
+ class GapController extends Logger {
26222
27112
  constructor(config, media, fragmentTracker, hls) {
27113
+ super('gap-controller', hls.logger);
26223
27114
  this.config = void 0;
26224
27115
  this.media = null;
26225
27116
  this.fragmentTracker = void 0;
@@ -26229,6 +27120,7 @@ class GapController {
26229
27120
  this.stalled = null;
26230
27121
  this.moved = false;
26231
27122
  this.seeking = false;
27123
+ this.ended = 0;
26232
27124
  this.config = config;
26233
27125
  this.media = media;
26234
27126
  this.fragmentTracker = fragmentTracker;
@@ -26246,7 +27138,7 @@ class GapController {
26246
27138
  *
26247
27139
  * @param lastCurrentTime - Previously read playhead position
26248
27140
  */
26249
- poll(lastCurrentTime, activeFrag) {
27141
+ poll(lastCurrentTime, activeFrag, levelDetails, state) {
26250
27142
  const {
26251
27143
  config,
26252
27144
  media,
@@ -26265,6 +27157,7 @@ class GapController {
26265
27157
 
26266
27158
  // The playhead is moving, no-op
26267
27159
  if (currentTime !== lastCurrentTime) {
27160
+ this.ended = 0;
26268
27161
  this.moved = true;
26269
27162
  if (!seeking) {
26270
27163
  this.nudgeRetry = 0;
@@ -26273,7 +27166,7 @@ class GapController {
26273
27166
  // The playhead is now moving, but was previously stalled
26274
27167
  if (this.stallReported) {
26275
27168
  const _stalledDuration = self.performance.now() - stalled;
26276
- logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
27169
+ this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
26277
27170
  this.stallReported = false;
26278
27171
  }
26279
27172
  this.stalled = null;
@@ -26309,7 +27202,6 @@ class GapController {
26309
27202
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
26310
27203
  // The addition poll gives the browser a chance to jump the gap for us
26311
27204
  if (!this.moved && this.stalled !== null) {
26312
- var _level$details;
26313
27205
  // There is no playable buffer (seeked, waiting for buffer)
26314
27206
  const isBuffered = bufferInfo.len > 0;
26315
27207
  if (!isBuffered && !nextStart) {
@@ -26321,9 +27213,8 @@ class GapController {
26321
27213
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
26322
27214
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
26323
27215
  // that begins over 1 target duration after the video start position.
26324
- const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
26325
- const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
26326
- const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
27216
+ const isLive = !!(levelDetails != null && levelDetails.live);
27217
+ const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
26327
27218
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
26328
27219
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
26329
27220
  if (!media.paused) {
@@ -26341,6 +27232,17 @@ class GapController {
26341
27232
  }
26342
27233
  const stalledDuration = tnow - stalled;
26343
27234
  if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
27235
+ // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
27236
+ if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
27237
+ if (stalledDuration < 1000 || this.ended) {
27238
+ return;
27239
+ }
27240
+ this.ended = currentTime;
27241
+ this.hls.trigger(Events.MEDIA_ENDED, {
27242
+ stalled: true
27243
+ });
27244
+ return;
27245
+ }
26344
27246
  // Report stalling after trying to fix
26345
27247
  this._reportStall(bufferInfo);
26346
27248
  if (!this.media) {
@@ -26384,7 +27286,7 @@ class GapController {
26384
27286
  // needs to cross some sort of threshold covering all source-buffers content
26385
27287
  // to start playing properly.
26386
27288
  if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
26387
- logger.warn('Trying to nudge playhead over buffer-hole');
27289
+ this.warn('Trying to nudge playhead over buffer-hole');
26388
27290
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
26389
27291
  // We only try to jump the hole if it's under the configured size
26390
27292
  // Reset stalled so to rearm watchdog timer
@@ -26408,7 +27310,7 @@ class GapController {
26408
27310
  // Report stalled error once
26409
27311
  this.stallReported = true;
26410
27312
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
26411
- logger.warn(error.message);
27313
+ this.warn(error.message);
26412
27314
  hls.trigger(Events.ERROR, {
26413
27315
  type: ErrorTypes.MEDIA_ERROR,
26414
27316
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -26476,7 +27378,7 @@ class GapController {
26476
27378
  }
26477
27379
  }
26478
27380
  const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
26479
- logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
27381
+ this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
26480
27382
  this.moved = true;
26481
27383
  this.stalled = null;
26482
27384
  media.currentTime = targetTime;
@@ -26517,7 +27419,7 @@ class GapController {
26517
27419
  const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
26518
27420
  // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
26519
27421
  const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
26520
- logger.warn(error.message);
27422
+ this.warn(error.message);
26521
27423
  media.currentTime = targetTime;
26522
27424
  hls.trigger(Events.ERROR, {
26523
27425
  type: ErrorTypes.MEDIA_ERROR,
@@ -26527,7 +27429,7 @@ class GapController {
26527
27429
  });
26528
27430
  } else {
26529
27431
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
26530
- logger.error(error.message);
27432
+ this.error(error.message);
26531
27433
  hls.trigger(Events.ERROR, {
26532
27434
  type: ErrorTypes.MEDIA_ERROR,
26533
27435
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -26542,7 +27444,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
26542
27444
 
26543
27445
  class StreamController extends BaseStreamController {
26544
27446
  constructor(hls, fragmentTracker, keyLoader) {
26545
- super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
27447
+ super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
26546
27448
  this.audioCodecSwap = false;
26547
27449
  this.gapController = null;
26548
27450
  this.level = -1;
@@ -26550,27 +27452,43 @@ class StreamController extends BaseStreamController {
26550
27452
  this.altAudio = false;
26551
27453
  this.audioOnly = false;
26552
27454
  this.fragPlaying = null;
26553
- this.onvplaying = null;
26554
- this.onvseeked = null;
26555
27455
  this.fragLastKbps = 0;
26556
27456
  this.couldBacktrack = false;
26557
27457
  this.backtrackFragment = null;
26558
27458
  this.audioCodecSwitch = false;
26559
27459
  this.videoBuffer = null;
26560
- this._registerListeners();
27460
+ this.onMediaPlaying = () => {
27461
+ // tick to speed up FRAG_CHANGED triggering
27462
+ this.tick();
27463
+ };
27464
+ this.onMediaSeeked = () => {
27465
+ const media = this.media;
27466
+ const currentTime = media ? media.currentTime : null;
27467
+ if (isFiniteNumber(currentTime)) {
27468
+ this.log(`Media seeked to ${currentTime.toFixed(3)}`);
27469
+ }
27470
+
27471
+ // If seeked was issued before buffer was appended do not tick immediately
27472
+ const bufferInfo = this.getMainFwdBufferInfo();
27473
+ if (bufferInfo === null || bufferInfo.len === 0) {
27474
+ this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
27475
+ return;
27476
+ }
27477
+
27478
+ // tick to speed up FRAG_CHANGED triggering
27479
+ this.tick();
27480
+ };
27481
+ this.registerListeners();
26561
27482
  }
26562
- _registerListeners() {
27483
+ registerListeners() {
27484
+ super.registerListeners();
26563
27485
  const {
26564
27486
  hls
26565
27487
  } = this;
26566
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
26567
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
26568
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
26569
27488
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
26570
27489
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
26571
27490
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
26572
27491
  hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26573
- hls.on(Events.ERROR, this.onError, this);
26574
27492
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
26575
27493
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
26576
27494
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -26578,17 +27496,14 @@ class StreamController extends BaseStreamController {
26578
27496
  hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
26579
27497
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
26580
27498
  }
26581
- _unregisterListeners() {
27499
+ unregisterListeners() {
27500
+ super.unregisterListeners();
26582
27501
  const {
26583
27502
  hls
26584
27503
  } = this;
26585
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
26586
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
26587
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
26588
27504
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
26589
27505
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
26590
27506
  hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
26591
- hls.off(Events.ERROR, this.onError, this);
26592
27507
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
26593
27508
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
26594
27509
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -26597,7 +27512,9 @@ class StreamController extends BaseStreamController {
26597
27512
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
26598
27513
  }
26599
27514
  onHandlerDestroying() {
26600
- this._unregisterListeners();
27515
+ // @ts-ignore
27516
+ this.onMediaPlaying = this.onMediaSeeked = null;
27517
+ this.unregisterListeners();
26601
27518
  super.onHandlerDestroying();
26602
27519
  }
26603
27520
  startLoad(startPosition) {
@@ -26717,7 +27634,7 @@ class StreamController extends BaseStreamController {
26717
27634
  if (this.altAudio && this.audioOnly) {
26718
27635
  return;
26719
27636
  }
26720
- if (!(levels != null && levels[level])) {
27637
+ if (!this.buffering || !(levels != null && levels[level])) {
26721
27638
  return;
26722
27639
  }
26723
27640
  const levelInfo = levels[level];
@@ -26925,20 +27842,17 @@ class StreamController extends BaseStreamController {
26925
27842
  onMediaAttached(event, data) {
26926
27843
  super.onMediaAttached(event, data);
26927
27844
  const media = data.media;
26928
- this.onvplaying = this.onMediaPlaying.bind(this);
26929
- this.onvseeked = this.onMediaSeeked.bind(this);
26930
- media.addEventListener('playing', this.onvplaying);
26931
- media.addEventListener('seeked', this.onvseeked);
27845
+ media.addEventListener('playing', this.onMediaPlaying);
27846
+ media.addEventListener('seeked', this.onMediaSeeked);
26932
27847
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
26933
27848
  }
26934
27849
  onMediaDetaching() {
26935
27850
  const {
26936
27851
  media
26937
27852
  } = this;
26938
- if (media && this.onvplaying && this.onvseeked) {
26939
- media.removeEventListener('playing', this.onvplaying);
26940
- media.removeEventListener('seeked', this.onvseeked);
26941
- this.onvplaying = this.onvseeked = null;
27853
+ if (media) {
27854
+ media.removeEventListener('playing', this.onMediaPlaying);
27855
+ media.removeEventListener('seeked', this.onMediaSeeked);
26942
27856
  this.videoBuffer = null;
26943
27857
  }
26944
27858
  this.fragPlaying = null;
@@ -26948,27 +27862,6 @@ class StreamController extends BaseStreamController {
26948
27862
  }
26949
27863
  super.onMediaDetaching();
26950
27864
  }
26951
- onMediaPlaying() {
26952
- // tick to speed up FRAG_CHANGED triggering
26953
- this.tick();
26954
- }
26955
- onMediaSeeked() {
26956
- const media = this.media;
26957
- const currentTime = media ? media.currentTime : null;
26958
- if (isFiniteNumber(currentTime)) {
26959
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
26960
- }
26961
-
26962
- // If seeked was issued before buffer was appended do not tick immediately
26963
- const bufferInfo = this.getMainFwdBufferInfo();
26964
- if (bufferInfo === null || bufferInfo.len === 0) {
26965
- this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
26966
- return;
26967
- }
26968
-
26969
- // tick to speed up FRAG_CHANGED triggering
26970
- this.tick();
26971
- }
26972
27865
  onManifestLoading() {
26973
27866
  // reset buffer on manifest loading
26974
27867
  this.log('Trigger BUFFER_RESET');
@@ -27260,8 +28153,10 @@ class StreamController extends BaseStreamController {
27260
28153
  }
27261
28154
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
27262
28155
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
27263
- const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
27264
- gapController.poll(this.lastCurrentTime, activeFrag);
28156
+ const state = this.state;
28157
+ const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
28158
+ const levelDetails = this.getLevelDetails();
28159
+ gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
27265
28160
  }
27266
28161
  this.lastCurrentTime = media.currentTime;
27267
28162
  }
@@ -27699,7 +28594,7 @@ class Hls {
27699
28594
  * Get the video-dev/hls.js package version.
27700
28595
  */
27701
28596
  static get version() {
27702
- return "1.5.5";
28597
+ return "1.5.6-0.canary.10003";
27703
28598
  }
27704
28599
 
27705
28600
  /**
@@ -27762,9 +28657,12 @@ class Hls {
27762
28657
  * The configuration object provided on player instantiation.
27763
28658
  */
27764
28659
  this.userConfig = void 0;
28660
+ /**
28661
+ * The logger functions used by this player instance, configured on player instantiation.
28662
+ */
28663
+ this.logger = void 0;
27765
28664
  this.coreComponents = void 0;
27766
28665
  this.networkControllers = void 0;
27767
- this.started = false;
27768
28666
  this._emitter = new EventEmitter();
27769
28667
  this._autoLevelCapping = -1;
27770
28668
  this._maxHdcpLevel = null;
@@ -27781,11 +28679,11 @@ class Hls {
27781
28679
  this._media = null;
27782
28680
  this.url = null;
27783
28681
  this.triggeringException = void 0;
27784
- enableLogs(userConfig.debug || false, 'Hls instance');
27785
- const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
28682
+ const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
28683
+ const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
27786
28684
  this.userConfig = userConfig;
27787
28685
  if (config.progressive) {
27788
- enableStreamingMode(config);
28686
+ enableStreamingMode(config, logger);
27789
28687
  }
27790
28688
 
27791
28689
  // core controllers and network loaders
@@ -27884,7 +28782,7 @@ class Hls {
27884
28782
  try {
27885
28783
  return this.emit(event, event, eventObject);
27886
28784
  } catch (error) {
27887
- logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
28785
+ this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
27888
28786
  // Prevent recursion in error event handlers that throw #5497
27889
28787
  if (!this.triggeringException) {
27890
28788
  this.triggeringException = true;
@@ -27910,7 +28808,7 @@ class Hls {
27910
28808
  * Dispose of the instance
27911
28809
  */
27912
28810
  destroy() {
27913
- logger.log('destroy');
28811
+ this.logger.log('destroy');
27914
28812
  this.trigger(Events.DESTROYING, undefined);
27915
28813
  this.detachMedia();
27916
28814
  this.removeAllListeners();
@@ -27931,7 +28829,7 @@ class Hls {
27931
28829
  * Attaches Hls.js to a media element
27932
28830
  */
27933
28831
  attachMedia(media) {
27934
- logger.log('attachMedia');
28832
+ this.logger.log('attachMedia');
27935
28833
  this._media = media;
27936
28834
  this.trigger(Events.MEDIA_ATTACHING, {
27937
28835
  media: media
@@ -27942,7 +28840,7 @@ class Hls {
27942
28840
  * Detach Hls.js from the media
27943
28841
  */
27944
28842
  detachMedia() {
27945
- logger.log('detachMedia');
28843
+ this.logger.log('detachMedia');
27946
28844
  this.trigger(Events.MEDIA_DETACHING, undefined);
27947
28845
  this._media = null;
27948
28846
  }
@@ -27959,7 +28857,7 @@ class Hls {
27959
28857
  });
27960
28858
  this._autoLevelCapping = -1;
27961
28859
  this._maxHdcpLevel = null;
27962
- logger.log(`loadSource:${loadingSource}`);
28860
+ this.logger.log(`loadSource:${loadingSource}`);
27963
28861
  if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
27964
28862
  this.detachMedia();
27965
28863
  this.attachMedia(media);
@@ -27978,8 +28876,7 @@ class Hls {
27978
28876
  * Defaults to -1 (None: starts from earliest point)
27979
28877
  */
27980
28878
  startLoad(startPosition = -1) {
27981
- logger.log(`startLoad(${startPosition})`);
27982
- this.started = true;
28879
+ this.logger.log(`startLoad(${startPosition})`);
27983
28880
  this.networkControllers.forEach(controller => {
27984
28881
  controller.startLoad(startPosition);
27985
28882
  });
@@ -27989,34 +28886,31 @@ class Hls {
27989
28886
  * Stop loading of any stream data.
27990
28887
  */
27991
28888
  stopLoad() {
27992
- logger.log('stopLoad');
27993
- this.started = false;
28889
+ this.logger.log('stopLoad');
27994
28890
  this.networkControllers.forEach(controller => {
27995
28891
  controller.stopLoad();
27996
28892
  });
27997
28893
  }
27998
28894
 
27999
28895
  /**
28000
- * Resumes stream controller segment loading if previously started.
28896
+ * Resumes stream controller segment loading after `pauseBuffering` has been called.
28001
28897
  */
28002
28898
  resumeBuffering() {
28003
- if (this.started) {
28004
- this.networkControllers.forEach(controller => {
28005
- if ('fragmentLoader' in controller) {
28006
- controller.startLoad(-1);
28007
- }
28008
- });
28009
- }
28899
+ this.networkControllers.forEach(controller => {
28900
+ if (controller.resumeBuffering) {
28901
+ controller.resumeBuffering();
28902
+ }
28903
+ });
28010
28904
  }
28011
28905
 
28012
28906
  /**
28013
- * Stops stream controller segment loading without changing 'started' state like stopLoad().
28907
+ * Prevents stream controller from loading new segments until `resumeBuffering` is called.
28014
28908
  * This allows for media buffering to be paused without interupting playlist loading.
28015
28909
  */
28016
28910
  pauseBuffering() {
28017
28911
  this.networkControllers.forEach(controller => {
28018
- if ('fragmentLoader' in controller) {
28019
- controller.stopLoad();
28912
+ if (controller.pauseBuffering) {
28913
+ controller.pauseBuffering();
28020
28914
  }
28021
28915
  });
28022
28916
  }
@@ -28025,7 +28919,7 @@ class Hls {
28025
28919
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
28026
28920
  */
28027
28921
  swapAudioCodec() {
28028
- logger.log('swapAudioCodec');
28922
+ this.logger.log('swapAudioCodec');
28029
28923
  this.streamController.swapAudioCodec();
28030
28924
  }
28031
28925
 
@@ -28036,7 +28930,7 @@ class Hls {
28036
28930
  * Automatic recovery of media-errors by this process is configurable.
28037
28931
  */
28038
28932
  recoverMediaError() {
28039
- logger.log('recoverMediaError');
28933
+ this.logger.log('recoverMediaError');
28040
28934
  const media = this._media;
28041
28935
  this.detachMedia();
28042
28936
  if (media) {
@@ -28066,7 +28960,7 @@ class Hls {
28066
28960
  * 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.
28067
28961
  */
28068
28962
  set currentLevel(newLevel) {
28069
- logger.log(`set currentLevel:${newLevel}`);
28963
+ this.logger.log(`set currentLevel:${newLevel}`);
28070
28964
  this.levelController.manualLevel = newLevel;
28071
28965
  this.streamController.immediateLevelSwitch();
28072
28966
  }
@@ -28085,7 +28979,7 @@ class Hls {
28085
28979
  * @param newLevel - Pass -1 for automatic level selection
28086
28980
  */
28087
28981
  set nextLevel(newLevel) {
28088
- logger.log(`set nextLevel:${newLevel}`);
28982
+ this.logger.log(`set nextLevel:${newLevel}`);
28089
28983
  this.levelController.manualLevel = newLevel;
28090
28984
  this.streamController.nextLevelSwitch();
28091
28985
  }
@@ -28104,7 +28998,7 @@ class Hls {
28104
28998
  * @param newLevel - Pass -1 for automatic level selection
28105
28999
  */
28106
29000
  set loadLevel(newLevel) {
28107
- logger.log(`set loadLevel:${newLevel}`);
29001
+ this.logger.log(`set loadLevel:${newLevel}`);
28108
29002
  this.levelController.manualLevel = newLevel;
28109
29003
  }
28110
29004
 
@@ -28135,7 +29029,7 @@ class Hls {
28135
29029
  * Sets "first-level", see getter.
28136
29030
  */
28137
29031
  set firstLevel(newLevel) {
28138
- logger.log(`set firstLevel:${newLevel}`);
29032
+ this.logger.log(`set firstLevel:${newLevel}`);
28139
29033
  this.levelController.firstLevel = newLevel;
28140
29034
  }
28141
29035
 
@@ -28160,7 +29054,7 @@ class Hls {
28160
29054
  * (determined from download of first segment)
28161
29055
  */
28162
29056
  set startLevel(newLevel) {
28163
- logger.log(`set startLevel:${newLevel}`);
29057
+ this.logger.log(`set startLevel:${newLevel}`);
28164
29058
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
28165
29059
  if (newLevel !== -1) {
28166
29060
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -28235,7 +29129,7 @@ class Hls {
28235
29129
  */
28236
29130
  set autoLevelCapping(newLevel) {
28237
29131
  if (this._autoLevelCapping !== newLevel) {
28238
- logger.log(`set autoLevelCapping:${newLevel}`);
29132
+ this.logger.log(`set autoLevelCapping:${newLevel}`);
28239
29133
  this._autoLevelCapping = newLevel;
28240
29134
  this.levelController.checkMaxAutoUpdated();
28241
29135
  }