hls.js 1.5.2-0.canary.9923 → 1.5.2

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 (44) hide show
  1. package/dist/hls-demo.js +0 -5
  2. package/dist/hls-demo.js.map +1 -1
  3. package/dist/hls.js +379 -431
  4. package/dist/hls.js.d.ts +14 -14
  5. package/dist/hls.js.map +1 -1
  6. package/dist/hls.light.js +217 -275
  7. package/dist/hls.light.js.map +1 -1
  8. package/dist/hls.light.min.js +1 -1
  9. package/dist/hls.light.min.js.map +1 -1
  10. package/dist/hls.light.mjs +219 -278
  11. package/dist/hls.light.mjs.map +1 -1
  12. package/dist/hls.min.js +1 -1
  13. package/dist/hls.min.js.map +1 -1
  14. package/dist/hls.mjs +350 -401
  15. package/dist/hls.mjs.map +1 -1
  16. package/dist/hls.worker.js +1 -1
  17. package/dist/hls.worker.js.map +1 -1
  18. package/package.json +9 -9
  19. package/src/controller/abr-controller.ts +2 -2
  20. package/src/controller/base-stream-controller.ts +16 -16
  21. package/src/controller/buffer-controller.ts +0 -6
  22. package/src/controller/eme-controller.ts +12 -6
  23. package/src/controller/latency-controller.ts +8 -7
  24. package/src/controller/level-controller.ts +18 -7
  25. package/src/controller/stream-controller.ts +14 -11
  26. package/src/controller/subtitle-stream-controller.ts +1 -6
  27. package/src/controller/subtitle-track-controller.ts +2 -4
  28. package/src/controller/timeline-controller.ts +26 -20
  29. package/src/crypt/aes-crypto.ts +2 -21
  30. package/src/crypt/decrypter.ts +18 -32
  31. package/src/crypt/fast-aes-key.ts +5 -24
  32. package/src/demux/audio/adts.ts +4 -9
  33. package/src/demux/sample-aes.ts +0 -2
  34. package/src/demux/transmuxer-interface.ts +12 -4
  35. package/src/demux/transmuxer.ts +3 -16
  36. package/src/demux/tsdemuxer.ts +17 -12
  37. package/src/loader/fragment-loader.ts +2 -9
  38. package/src/loader/key-loader.ts +0 -2
  39. package/src/loader/level-key.ts +9 -10
  40. package/src/remux/mp4-remuxer.ts +3 -4
  41. package/src/types/demuxer.ts +0 -1
  42. package/src/utils/codecs.ts +4 -33
  43. package/src/crypt/decrypter-aes-mode.ts +0 -4
  44. package/src/utils/encryption-methods-util.ts +0 -21
package/dist/hls.mjs CHANGED
@@ -411,7 +411,7 @@ function enableLogs(debugConfig, id) {
411
411
  // Some browsers don't allow to use bind on console object anyway
412
412
  // fallback to default if needed
413
413
  try {
414
- exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.2-0.canary.9923"}`);
414
+ exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.2"}`);
415
415
  } catch (e) {
416
416
  exportedLogger = fakeLogger;
417
417
  }
@@ -1036,26 +1036,6 @@ function strToUtf8array(str) {
1036
1036
  return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
1037
1037
  }
1038
1038
 
1039
- var DecrypterAesMode = {
1040
- cbc: 0,
1041
- ctr: 1
1042
- };
1043
-
1044
- function isFullSegmentEncryption(method) {
1045
- return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
1046
- }
1047
- function getAesModeFromFullSegmentMethod(method) {
1048
- switch (method) {
1049
- case 'AES-128':
1050
- case 'AES-256':
1051
- return DecrypterAesMode.cbc;
1052
- case 'AES-256-CTR':
1053
- return DecrypterAesMode.ctr;
1054
- default:
1055
- throw new Error(`invalid full segment method ${method}`);
1056
- }
1057
- }
1058
-
1059
1039
  /** returns `undefined` is `self` is missing, e.g. in node */
1060
1040
  const optionalSelf = typeof self !== 'undefined' ? self : undefined;
1061
1041
 
@@ -2694,12 +2674,12 @@ class LevelKey {
2694
2674
  this.keyFormatVersions = formatversions;
2695
2675
  this.iv = iv;
2696
2676
  this.encrypted = method ? method !== 'NONE' : false;
2697
- this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
2677
+ this.isCommonEncryption = this.encrypted && method !== 'AES-128';
2698
2678
  }
2699
2679
  isSupported() {
2700
2680
  // If it's Segment encryption or No encryption, just select that key system
2701
2681
  if (this.method) {
2702
- if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
2682
+ if (this.method === 'AES-128' || this.method === 'NONE') {
2703
2683
  return true;
2704
2684
  }
2705
2685
  if (this.keyFormat === 'identity') {
@@ -2721,13 +2701,14 @@ class LevelKey {
2721
2701
  if (!this.encrypted || !this.uri) {
2722
2702
  return null;
2723
2703
  }
2724
- if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
2704
+ if (this.method === 'AES-128' && this.uri && !this.iv) {
2725
2705
  if (typeof sn !== 'number') {
2726
2706
  // We are fetching decryption data for a initialization segment
2727
- // If the segment was encrypted with AES-128/256
2707
+ // If the segment was encrypted with AES-128
2728
2708
  // It must have an IV defined. We cannot substitute the Segment Number in.
2729
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2730
-
2709
+ if (this.method === 'AES-128' && !this.iv) {
2710
+ logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2711
+ }
2731
2712
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
2732
2713
  sn = 0;
2733
2714
  }
@@ -3006,28 +2987,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
3006
2987
  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
3007
2988
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
3008
2989
  }
2990
+
2991
+ // Idealy fLaC and Opus would be first (spec-compliant) but
2992
+ // some browsers will report that fLaC is supported then fail.
2993
+ // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3009
2994
  const codecsToCheck = {
3010
- // Idealy fLaC and Opus would be first (spec-compliant) but
3011
- // some browsers will report that fLaC is supported then fail.
3012
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3013
2995
  flac: ['flac', 'fLaC', 'FLAC'],
3014
- opus: ['opus', 'Opus'],
3015
- // Replace audio codec info if browser does not support mp4a.40.34,
3016
- // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
3017
- 'mp4a.40.34': ['mp3']
2996
+ opus: ['opus', 'Opus']
3018
2997
  }[lowerCaseCodec];
3019
2998
  for (let i = 0; i < codecsToCheck.length; i++) {
3020
- var _getMediaSource;
3021
2999
  if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
3022
3000
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
3023
3001
  return codecsToCheck[i];
3024
- } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
3025
- return '';
3026
3002
  }
3027
3003
  }
3028
3004
  return lowerCaseCodec;
3029
3005
  }
3030
- const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
3006
+ const AUDIO_CODEC_REGEXP = /flac|opus/i;
3031
3007
  function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
3032
3008
  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
3033
3009
  }
@@ -3050,16 +3026,6 @@ function convertAVC1ToAVCOTI(codec) {
3050
3026
  }
3051
3027
  return codec;
3052
3028
  }
3053
- function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
3054
- const MediaSource = getMediaSource(preferManagedMediaSource) || {
3055
- isTypeSupported: () => false
3056
- };
3057
- return {
3058
- mpeg: MediaSource.isTypeSupported('audio/mpeg'),
3059
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
3060
- ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
3061
- };
3062
- }
3063
3029
 
3064
3030
  const MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g;
3065
3031
  const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -4726,47 +4692,7 @@ class LatencyController {
4726
4692
  this.currentTime = 0;
4727
4693
  this.stallCount = 0;
4728
4694
  this._latency = null;
4729
- this.onTimeupdate = () => {
4730
- const {
4731
- media,
4732
- levelDetails
4733
- } = this;
4734
- if (!media || !levelDetails) {
4735
- return;
4736
- }
4737
- this.currentTime = media.currentTime;
4738
- const latency = this.computeLatency();
4739
- if (latency === null) {
4740
- return;
4741
- }
4742
- this._latency = latency;
4743
-
4744
- // Adapt playbackRate to meet target latency in low-latency mode
4745
- const {
4746
- lowLatencyMode,
4747
- maxLiveSyncPlaybackRate
4748
- } = this.config;
4749
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4750
- return;
4751
- }
4752
- const targetLatency = this.targetLatency;
4753
- if (targetLatency === null) {
4754
- return;
4755
- }
4756
- const distanceFromTarget = latency - targetLatency;
4757
- // Only adjust playbackRate when within one target duration of targetLatency
4758
- // and more than one second from under-buffering.
4759
- // Playback further than one target duration from target can be considered DVR playback.
4760
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4761
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4762
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4763
- const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4764
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4765
- media.playbackRate = Math.min(max, Math.max(1, rate));
4766
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4767
- media.playbackRate = 1;
4768
- }
4769
- };
4695
+ this.timeupdateHandler = () => this.timeupdate();
4770
4696
  this.hls = hls;
4771
4697
  this.config = hls.config;
4772
4698
  this.registerListeners();
@@ -4858,7 +4784,7 @@ class LatencyController {
4858
4784
  this.onMediaDetaching();
4859
4785
  this.levelDetails = null;
4860
4786
  // @ts-ignore
4861
- this.hls = this.onTimeupdate = null;
4787
+ this.hls = this.timeupdateHandler = null;
4862
4788
  }
4863
4789
  registerListeners() {
4864
4790
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -4876,11 +4802,11 @@ class LatencyController {
4876
4802
  }
4877
4803
  onMediaAttached(event, data) {
4878
4804
  this.media = data.media;
4879
- this.media.addEventListener('timeupdate', this.onTimeupdate);
4805
+ this.media.addEventListener('timeupdate', this.timeupdateHandler);
4880
4806
  }
4881
4807
  onMediaDetaching() {
4882
4808
  if (this.media) {
4883
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4809
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4884
4810
  this.media = null;
4885
4811
  }
4886
4812
  }
@@ -4894,10 +4820,10 @@ class LatencyController {
4894
4820
  }) {
4895
4821
  this.levelDetails = details;
4896
4822
  if (details.advanced) {
4897
- this.onTimeupdate();
4823
+ this.timeupdate();
4898
4824
  }
4899
4825
  if (!details.live && this.media) {
4900
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4826
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4901
4827
  }
4902
4828
  }
4903
4829
  onError(event, data) {
@@ -4910,6 +4836,47 @@ class LatencyController {
4910
4836
  logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
4911
4837
  }
4912
4838
  }
4839
+ timeupdate() {
4840
+ const {
4841
+ media,
4842
+ levelDetails
4843
+ } = this;
4844
+ if (!media || !levelDetails) {
4845
+ return;
4846
+ }
4847
+ this.currentTime = media.currentTime;
4848
+ const latency = this.computeLatency();
4849
+ if (latency === null) {
4850
+ return;
4851
+ }
4852
+ this._latency = latency;
4853
+
4854
+ // Adapt playbackRate to meet target latency in low-latency mode
4855
+ const {
4856
+ lowLatencyMode,
4857
+ maxLiveSyncPlaybackRate
4858
+ } = this.config;
4859
+ if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4860
+ return;
4861
+ }
4862
+ const targetLatency = this.targetLatency;
4863
+ if (targetLatency === null) {
4864
+ return;
4865
+ }
4866
+ const distanceFromTarget = latency - targetLatency;
4867
+ // Only adjust playbackRate when within one target duration of targetLatency
4868
+ // and more than one second from under-buffering.
4869
+ // Playback further than one target duration from target can be considered DVR playback.
4870
+ const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4871
+ const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4872
+ if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4873
+ const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4874
+ const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4875
+ media.playbackRate = Math.min(max, Math.max(1, rate));
4876
+ } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4877
+ media.playbackRate = 1;
4878
+ }
4879
+ }
4913
4880
  estimateLiveEdge() {
4914
4881
  const {
4915
4882
  levelDetails
@@ -6910,7 +6877,7 @@ class AbrController {
6910
6877
  const bwEstimate = this.getBwEstimate();
6911
6878
  const levels = hls.levels;
6912
6879
  const level = levels[frag.level];
6913
- const expectedLen = stats.total || Math.max(stats.loaded, Math.round(duration * level.maxBitrate / 8));
6880
+ const expectedLen = stats.total || Math.max(stats.loaded, Math.round(duration * level.averageBitrate / 8));
6914
6881
  let timeStreaming = loadedFirstByte ? timeLoading - ttfb : timeLoading;
6915
6882
  if (timeStreaming < 1 && loadedFirstByte) {
6916
6883
  timeStreaming = Math.min(timeLoading, stats.loaded * 8 / bwEstimate);
@@ -6953,7 +6920,7 @@ class AbrController {
6953
6920
  // If there has been no loading progress, sample TTFB
6954
6921
  this.bwEstimator.sampleTTFB(timeLoading);
6955
6922
  }
6956
- const nextLoadLevelBitrate = levels[nextLoadLevel].bitrate;
6923
+ const nextLoadLevelBitrate = levels[nextLoadLevel].maxBitrate;
6957
6924
  if (this.getBwEstimate() * this.hls.config.abrBandWidthUpFactor > nextLoadLevelBitrate) {
6958
6925
  this.resetEstimator(nextLoadLevelBitrate);
6959
6926
  }
@@ -8582,8 +8549,8 @@ function createLoaderContext(frag, part = null) {
8582
8549
  var _frag$decryptdata;
8583
8550
  let byteRangeStart = start;
8584
8551
  let byteRangeEnd = end;
8585
- if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
8586
- // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
8552
+ if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
8553
+ // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
8587
8554
  // has the unencrypted size specified in the range.
8588
8555
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
8589
8556
  const fragmentLen = end - start;
@@ -8616,9 +8583,6 @@ function createGapLoadError(frag, part) {
8616
8583
  (part ? part : frag).stats.aborted = true;
8617
8584
  return new LoadError(errorData);
8618
8585
  }
8619
- function isMethodFullSegmentAesCbc(method) {
8620
- return method === 'AES-128' || method === 'AES-256';
8621
- }
8622
8586
  class LoadError extends Error {
8623
8587
  constructor(data) {
8624
8588
  super(data.error.message);
@@ -8628,61 +8592,33 @@ class LoadError extends Error {
8628
8592
  }
8629
8593
 
8630
8594
  class AESCrypto {
8631
- constructor(subtle, iv, aesMode) {
8595
+ constructor(subtle, iv) {
8632
8596
  this.subtle = void 0;
8633
8597
  this.aesIV = void 0;
8634
- this.aesMode = void 0;
8635
8598
  this.subtle = subtle;
8636
8599
  this.aesIV = iv;
8637
- this.aesMode = aesMode;
8638
8600
  }
8639
8601
  decrypt(data, key) {
8640
- switch (this.aesMode) {
8641
- case DecrypterAesMode.cbc:
8642
- return this.subtle.decrypt({
8643
- name: 'AES-CBC',
8644
- iv: this.aesIV
8645
- }, key, data);
8646
- case DecrypterAesMode.ctr:
8647
- return this.subtle.decrypt({
8648
- name: 'AES-CTR',
8649
- counter: this.aesIV,
8650
- length: 64
8651
- },
8652
- //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
8653
- key, data);
8654
- default:
8655
- throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
8656
- }
8602
+ return this.subtle.decrypt({
8603
+ name: 'AES-CBC',
8604
+ iv: this.aesIV
8605
+ }, key, data);
8657
8606
  }
8658
8607
  }
8659
8608
 
8660
8609
  class FastAESKey {
8661
- constructor(subtle, key, aesMode) {
8610
+ constructor(subtle, key) {
8662
8611
  this.subtle = void 0;
8663
8612
  this.key = void 0;
8664
- this.aesMode = void 0;
8665
8613
  this.subtle = subtle;
8666
8614
  this.key = key;
8667
- this.aesMode = aesMode;
8668
8615
  }
8669
8616
  expandKey() {
8670
- const subtleAlgoName = getSubtleAlgoName(this.aesMode);
8671
8617
  return this.subtle.importKey('raw', this.key, {
8672
- name: subtleAlgoName
8618
+ name: 'AES-CBC'
8673
8619
  }, false, ['encrypt', 'decrypt']);
8674
8620
  }
8675
8621
  }
8676
- function getSubtleAlgoName(aesMode) {
8677
- switch (aesMode) {
8678
- case DecrypterAesMode.cbc:
8679
- return 'AES-CBC';
8680
- case DecrypterAesMode.ctr:
8681
- return 'AES-CTR';
8682
- default:
8683
- throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
8684
- }
8685
- }
8686
8622
 
8687
8623
  // PKCS7
8688
8624
  function removePadding(array) {
@@ -8932,8 +8868,7 @@ class Decrypter {
8932
8868
  this.currentIV = null;
8933
8869
  this.currentResult = null;
8934
8870
  this.useSoftware = void 0;
8935
- this.enableSoftwareAES = void 0;
8936
- this.enableSoftwareAES = config.enableSoftwareAES;
8871
+ this.useSoftware = config.enableSoftwareAES;
8937
8872
  this.removePKCS7Padding = removePKCS7Padding;
8938
8873
  // built in decryptor expects PKCS7 padding
8939
8874
  if (removePKCS7Padding) {
@@ -8946,7 +8881,9 @@ class Decrypter {
8946
8881
  /* no-op */
8947
8882
  }
8948
8883
  }
8949
- this.useSoftware = this.subtle === null;
8884
+ if (this.subtle === null) {
8885
+ this.useSoftware = true;
8886
+ }
8950
8887
  }
8951
8888
  destroy() {
8952
8889
  this.subtle = null;
@@ -8984,10 +8921,10 @@ class Decrypter {
8984
8921
  this.softwareDecrypter = null;
8985
8922
  }
8986
8923
  }
8987
- decrypt(data, key, iv, aesMode) {
8924
+ decrypt(data, key, iv) {
8988
8925
  if (this.useSoftware) {
8989
8926
  return new Promise((resolve, reject) => {
8990
- this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
8927
+ this.softwareDecrypt(new Uint8Array(data), key, iv);
8991
8928
  const decryptResult = this.flush();
8992
8929
  if (decryptResult) {
8993
8930
  resolve(decryptResult.buffer);
@@ -8996,21 +8933,17 @@ class Decrypter {
8996
8933
  }
8997
8934
  });
8998
8935
  }
8999
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
8936
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
9000
8937
  }
9001
8938
 
9002
8939
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
9003
8940
  // data is handled in the flush() call
9004
- softwareDecrypt(data, key, iv, aesMode) {
8941
+ softwareDecrypt(data, key, iv) {
9005
8942
  const {
9006
8943
  currentIV,
9007
8944
  currentResult,
9008
8945
  remainderData
9009
8946
  } = this;
9010
- if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
9011
- logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
9012
- return null;
9013
- }
9014
8947
  this.logOnce('JS AES decrypt');
9015
8948
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
9016
8949
  // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
@@ -9043,11 +8976,11 @@ class Decrypter {
9043
8976
  }
9044
8977
  return result;
9045
8978
  }
9046
- webCryptoDecrypt(data, key, iv, aesMode) {
8979
+ webCryptoDecrypt(data, key, iv) {
9047
8980
  const subtle = this.subtle;
9048
8981
  if (this.key !== key || !this.fastAesKey) {
9049
8982
  this.key = key;
9050
- this.fastAesKey = new FastAESKey(subtle, key, aesMode);
8983
+ this.fastAesKey = new FastAESKey(subtle, key);
9051
8984
  }
9052
8985
  return this.fastAesKey.expandKey().then(aesKey => {
9053
8986
  // decrypt using web crypto
@@ -9055,25 +8988,22 @@ class Decrypter {
9055
8988
  return Promise.reject(new Error('web crypto not initialized'));
9056
8989
  }
9057
8990
  this.logOnce('WebCrypto AES decrypt');
9058
- const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
8991
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv));
9059
8992
  return crypto.decrypt(data.buffer, aesKey);
9060
8993
  }).catch(err => {
9061
8994
  logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
9062
- return this.onWebCryptoError(data, key, iv, aesMode);
8995
+ return this.onWebCryptoError(data, key, iv);
9063
8996
  });
9064
8997
  }
9065
- onWebCryptoError(data, key, iv, aesMode) {
9066
- const enableSoftwareAES = this.enableSoftwareAES;
9067
- if (enableSoftwareAES) {
9068
- this.useSoftware = true;
9069
- this.logEnabled = true;
9070
- this.softwareDecrypt(data, key, iv, aesMode);
9071
- const decryptResult = this.flush();
9072
- if (decryptResult) {
9073
- return decryptResult.buffer;
9074
- }
8998
+ onWebCryptoError(data, key, iv) {
8999
+ this.useSoftware = true;
9000
+ this.logEnabled = true;
9001
+ this.softwareDecrypt(data, key, iv);
9002
+ const decryptResult = this.flush();
9003
+ if (decryptResult) {
9004
+ return decryptResult.buffer;
9075
9005
  }
9076
- throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
9006
+ throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
9077
9007
  }
9078
9008
  getValidChunk(data) {
9079
9009
  let currentChunk = data;
@@ -9149,59 +9079,11 @@ class BaseStreamController extends TaskLoop {
9149
9079
  this.startFragRequested = false;
9150
9080
  this.decrypter = void 0;
9151
9081
  this.initPTS = [];
9082
+ this.onvseeking = null;
9083
+ this.onvended = null;
9152
9084
  this.logPrefix = '';
9153
9085
  this.log = void 0;
9154
9086
  this.warn = void 0;
9155
- this.onMediaSeeking = () => {
9156
- const {
9157
- config,
9158
- fragCurrent,
9159
- media,
9160
- mediaBuffer,
9161
- state
9162
- } = this;
9163
- const currentTime = media ? media.currentTime : 0;
9164
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9165
- this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9166
- if (this.state === State.ENDED) {
9167
- this.resetLoadingState();
9168
- } else if (fragCurrent) {
9169
- // Seeking while frag load is in progress
9170
- const tolerance = config.maxFragLookUpTolerance;
9171
- const fragStartOffset = fragCurrent.start - tolerance;
9172
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9173
- // if seeking out of buffered range or into new one
9174
- if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9175
- const pastFragment = currentTime > fragEndOffset;
9176
- // if the seek position is outside the current fragment range
9177
- if (currentTime < fragStartOffset || pastFragment) {
9178
- if (pastFragment && fragCurrent.loader) {
9179
- this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9180
- fragCurrent.abortRequests();
9181
- this.resetLoadingState();
9182
- }
9183
- this.fragPrevious = null;
9184
- }
9185
- }
9186
- }
9187
- if (media) {
9188
- // Remove gap fragments
9189
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9190
- this.lastCurrentTime = currentTime;
9191
- }
9192
-
9193
- // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9194
- if (!this.loadedmetadata && !bufferInfo.len) {
9195
- this.nextLoadPosition = this.startPosition = currentTime;
9196
- }
9197
-
9198
- // Async tick to speed up processing
9199
- this.tickImmediate();
9200
- };
9201
- this.onMediaEnded = () => {
9202
- // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9203
- this.startPosition = this.lastCurrentTime = 0;
9204
- };
9205
9087
  this.playlistType = playlistType;
9206
9088
  this.logPrefix = logPrefix;
9207
9089
  this.log = logger.log.bind(logger, `${logPrefix}:`);
@@ -9266,8 +9148,10 @@ class BaseStreamController extends TaskLoop {
9266
9148
  }
9267
9149
  onMediaAttached(event, data) {
9268
9150
  const media = this.media = this.mediaBuffer = data.media;
9269
- media.addEventListener('seeking', this.onMediaSeeking);
9270
- media.addEventListener('ended', this.onMediaEnded);
9151
+ this.onvseeking = this.onMediaSeeking.bind(this);
9152
+ this.onvended = this.onMediaEnded.bind(this);
9153
+ media.addEventListener('seeking', this.onvseeking);
9154
+ media.addEventListener('ended', this.onvended);
9271
9155
  const config = this.config;
9272
9156
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
9273
9157
  this.startLoad(config.startPosition);
@@ -9281,9 +9165,10 @@ class BaseStreamController extends TaskLoop {
9281
9165
  }
9282
9166
 
9283
9167
  // remove video listeners
9284
- if (media) {
9285
- media.removeEventListener('seeking', this.onMediaSeeking);
9286
- media.removeEventListener('ended', this.onMediaEnded);
9168
+ if (media && this.onvseeking && this.onvended) {
9169
+ media.removeEventListener('seeking', this.onvseeking);
9170
+ media.removeEventListener('ended', this.onvended);
9171
+ this.onvseeking = this.onvended = null;
9287
9172
  }
9288
9173
  if (this.keyLoader) {
9289
9174
  this.keyLoader.detach();
@@ -9293,6 +9178,56 @@ class BaseStreamController extends TaskLoop {
9293
9178
  this.fragmentTracker.removeAllFragments();
9294
9179
  this.stopLoad();
9295
9180
  }
9181
+ onMediaSeeking() {
9182
+ const {
9183
+ config,
9184
+ fragCurrent,
9185
+ media,
9186
+ mediaBuffer,
9187
+ state
9188
+ } = this;
9189
+ const currentTime = media ? media.currentTime : 0;
9190
+ const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9191
+ this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9192
+ if (this.state === State.ENDED) {
9193
+ this.resetLoadingState();
9194
+ } else if (fragCurrent) {
9195
+ // Seeking while frag load is in progress
9196
+ const tolerance = config.maxFragLookUpTolerance;
9197
+ const fragStartOffset = fragCurrent.start - tolerance;
9198
+ const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9199
+ // if seeking out of buffered range or into new one
9200
+ if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9201
+ const pastFragment = currentTime > fragEndOffset;
9202
+ // if the seek position is outside the current fragment range
9203
+ if (currentTime < fragStartOffset || pastFragment) {
9204
+ if (pastFragment && fragCurrent.loader) {
9205
+ this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9206
+ fragCurrent.abortRequests();
9207
+ this.resetLoadingState();
9208
+ }
9209
+ this.fragPrevious = null;
9210
+ }
9211
+ }
9212
+ }
9213
+ if (media) {
9214
+ // Remove gap fragments
9215
+ this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9216
+ this.lastCurrentTime = currentTime;
9217
+ }
9218
+
9219
+ // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9220
+ if (!this.loadedmetadata && !bufferInfo.len) {
9221
+ this.nextLoadPosition = this.startPosition = currentTime;
9222
+ }
9223
+
9224
+ // Async tick to speed up processing
9225
+ this.tickImmediate();
9226
+ }
9227
+ onMediaEnded() {
9228
+ // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9229
+ this.startPosition = this.lastCurrentTime = 0;
9230
+ }
9296
9231
  onManifestLoaded(event, data) {
9297
9232
  this.startTimeOffset = data.startTimeOffset;
9298
9233
  this.initPTS = [];
@@ -9302,7 +9237,7 @@ class BaseStreamController extends TaskLoop {
9302
9237
  this.stopLoad();
9303
9238
  super.onHandlerDestroying();
9304
9239
  // @ts-ignore
9305
- this.hls = this.onMediaSeeking = this.onMediaEnded = null;
9240
+ this.hls = null;
9306
9241
  }
9307
9242
  onHandlerDestroyed() {
9308
9243
  this.state = State.STOPPED;
@@ -9433,10 +9368,10 @@ class BaseStreamController extends TaskLoop {
9433
9368
  const decryptData = frag.decryptdata;
9434
9369
 
9435
9370
  // check to see if the payload needs to be decrypted
9436
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
9371
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
9437
9372
  const startTime = self.performance.now();
9438
9373
  // decrypt init segment data
9439
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
9374
+ return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
9440
9375
  hls.trigger(Events.ERROR, {
9441
9376
  type: ErrorTypes.MEDIA_ERROR,
9442
9377
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -10602,7 +10537,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
10602
10537
  */
10603
10538
  function getAudioConfig(observer, data, offset, audioCodec) {
10604
10539
  let adtsObjectType;
10605
- let originalAdtsObjectType;
10606
10540
  let adtsExtensionSamplingIndex;
10607
10541
  let adtsChannelConfig;
10608
10542
  let config;
@@ -10610,7 +10544,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10610
10544
  const manifestCodec = audioCodec;
10611
10545
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
10612
10546
  // byte 2
10613
- adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10547
+ adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
10614
10548
  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
10615
10549
  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
10616
10550
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
@@ -10627,8 +10561,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10627
10561
  // byte 3
10628
10562
  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
10629
10563
  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
10630
- // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
10631
- if (/firefox|palemoon/i.test(userAgent)) {
10564
+ // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
10565
+ if (/firefox/i.test(userAgent)) {
10632
10566
  if (adtsSamplingIndex >= 6) {
10633
10567
  adtsObjectType = 5;
10634
10568
  config = new Array(4);
@@ -10722,7 +10656,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
10722
10656
  samplerate: adtsSamplingRates[adtsSamplingIndex],
10723
10657
  channelCount: adtsChannelConfig,
10724
10658
  codec: 'mp4a.40.' + adtsObjectType,
10725
- parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
10726
10659
  manifestCodec
10727
10660
  };
10728
10661
  }
@@ -10777,8 +10710,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
10777
10710
  track.channelCount = config.channelCount;
10778
10711
  track.codec = config.codec;
10779
10712
  track.manifestCodec = config.manifestCodec;
10780
- track.parsedCodec = config.parsedCodec;
10781
- logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10713
+ logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
10782
10714
  }
10783
10715
  }
10784
10716
  function getFrameDuration(samplerate) {
@@ -11954,7 +11886,7 @@ class SampleAesDecrypter {
11954
11886
  });
11955
11887
  }
11956
11888
  decryptBuffer(encryptedData) {
11957
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
11889
+ return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
11958
11890
  }
11959
11891
 
11960
11892
  // AAC - encrypt all full 16 bytes blocks starting from offset 16
@@ -14198,7 +14130,7 @@ class MP4Remuxer {
14198
14130
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
14199
14131
  for (let j = 0; j < missing; j++) {
14200
14132
  const newStamp = Math.max(nextPts, 0);
14201
- let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14133
+ let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
14202
14134
  if (!fillFrame) {
14203
14135
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
14204
14136
  fillFrame = sample.unit.subarray();
@@ -14326,7 +14258,7 @@ class MP4Remuxer {
14326
14258
  // samples count of this segment's duration
14327
14259
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
14328
14260
  // silent frame
14329
- const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
14261
+ const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
14330
14262
  logger.warn('[mp4-remuxer]: remux empty Audio');
14331
14263
  // Can't remux if we can't generate a silent frame...
14332
14264
  if (!silentFrame) {
@@ -14720,15 +14652,13 @@ class Transmuxer {
14720
14652
  initSegmentData
14721
14653
  } = transmuxConfig;
14722
14654
  const keyData = getEncryptionType(uintData, decryptdata);
14723
- if (keyData && isFullSegmentEncryption(keyData.method)) {
14655
+ if (keyData && keyData.method === 'AES-128') {
14724
14656
  const decrypter = this.getDecrypter();
14725
- const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
14726
-
14727
14657
  // Software decryption is synchronous; webCrypto is not
14728
14658
  if (decrypter.isSync()) {
14729
14659
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
14730
14660
  // data is handled in the flush() call
14731
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
14661
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
14732
14662
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
14733
14663
  const loadingParts = chunkMeta.part > -1;
14734
14664
  if (loadingParts) {
@@ -14740,7 +14670,7 @@ class Transmuxer {
14740
14670
  }
14741
14671
  uintData = new Uint8Array(decryptedData);
14742
14672
  } else {
14743
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
14673
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
14744
14674
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
14745
14675
  // the decrypted data has been transmuxed
14746
14676
  const result = this.push(decryptedData, null, chunkMeta);
@@ -15394,7 +15324,14 @@ class TransmuxerInterface {
15394
15324
  this.observer = new EventEmitter();
15395
15325
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
15396
15326
  this.observer.on(Events.ERROR, forwardMessage);
15397
- const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
15327
+ const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
15328
+ isTypeSupported: () => false
15329
+ };
15330
+ const m2tsTypeSupported = {
15331
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
15332
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
15333
+ ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
15334
+ };
15398
15335
 
15399
15336
  // navigator.vendor is not always available in Web Worker
15400
15337
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -17046,10 +16983,10 @@ class SubtitleStreamController extends BaseStreamController {
17046
16983
  return;
17047
16984
  }
17048
16985
  // check to see if the payload needs to be decrypted
17049
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
16986
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
17050
16987
  const startTime = performance.now();
17051
16988
  // decrypt the subtitles
17052
- this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
16989
+ this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
17053
16990
  hls.trigger(Events.ERROR, {
17054
16991
  type: ErrorTypes.MEDIA_ERROR,
17055
16992
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -17192,10 +17129,10 @@ class SubtitleTrackController extends BasePlaylistController {
17192
17129
  this.currentTrack = null;
17193
17130
  this.selectDefaultTrack = true;
17194
17131
  this.queuedDefaultTrack = -1;
17132
+ this.asyncPollTrackChange = () => this.pollTrackChange(0);
17195
17133
  this.useTextTrackPolling = false;
17196
17134
  this.subtitlePollingInterval = -1;
17197
17135
  this._subtitleDisplay = true;
17198
- this.asyncPollTrackChange = () => this.pollTrackChange(0);
17199
17136
  this.onTextTracksChanged = () => {
17200
17137
  if (!this.useTextTrackPolling) {
17201
17138
  self.clearInterval(this.subtitlePollingInterval);
@@ -17229,7 +17166,6 @@ class SubtitleTrackController extends BasePlaylistController {
17229
17166
  this.tracks.length = 0;
17230
17167
  this.tracksInGroup.length = 0;
17231
17168
  this.currentTrack = null;
17232
- // @ts-ignore
17233
17169
  this.onTextTracksChanged = this.asyncPollTrackChange = null;
17234
17170
  super.destroy();
17235
17171
  }
@@ -17790,12 +17726,6 @@ class BufferController {
17790
17726
  this.lastMpegAudioChunk = null;
17791
17727
  // @ts-ignore
17792
17728
  this.hls = null;
17793
- // @ts-ignore
17794
- this._onMediaSourceOpen = this._onMediaSourceClose = null;
17795
- // @ts-ignore
17796
- this._onMediaSourceEnded = null;
17797
- // @ts-ignore
17798
- this._onStartStreaming = this._onEndStreaming = null;
17799
17729
  }
17800
17730
  registerListeners() {
17801
17731
  const {
@@ -21060,12 +20990,14 @@ class TimelineController {
21060
20990
  this.cea608Parser1 = this.cea608Parser2 = undefined;
21061
20991
  }
21062
20992
  initCea608Parsers() {
21063
- const channel1 = new OutputFilter(this, 'textTrack1');
21064
- const channel2 = new OutputFilter(this, 'textTrack2');
21065
- const channel3 = new OutputFilter(this, 'textTrack3');
21066
- const channel4 = new OutputFilter(this, 'textTrack4');
21067
- this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
21068
- this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
20993
+ if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) {
20994
+ const channel1 = new OutputFilter(this, 'textTrack1');
20995
+ const channel2 = new OutputFilter(this, 'textTrack2');
20996
+ const channel3 = new OutputFilter(this, 'textTrack3');
20997
+ const channel4 = new OutputFilter(this, 'textTrack4');
20998
+ this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
20999
+ this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
21000
+ }
21069
21001
  }
21070
21002
  addCues(trackName, startTime, endTime, screen, cueRanges) {
21071
21003
  // skip cues which overlap more than 50% with previously parsed time ranges
@@ -21348,23 +21280,26 @@ class TimelineController {
21348
21280
  return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
21349
21281
  }
21350
21282
  onFragLoading(event, data) {
21283
+ this.initCea608Parsers();
21284
+ const {
21285
+ cea608Parser1,
21286
+ cea608Parser2,
21287
+ lastCc,
21288
+ lastSn,
21289
+ lastPartIndex
21290
+ } = this;
21291
+ if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
21292
+ return;
21293
+ }
21351
21294
  // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
21352
- if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
21295
+ if (data.frag.type === PlaylistLevelType.MAIN) {
21353
21296
  var _data$part$index, _data$part;
21354
- const {
21355
- cea608Parser1,
21356
- cea608Parser2,
21357
- lastSn
21358
- } = this;
21359
- if (!cea608Parser1 || !cea608Parser2) {
21360
- return;
21361
- }
21362
21297
  const {
21363
21298
  cc,
21364
21299
  sn
21365
21300
  } = data.frag;
21366
- const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
21367
- if (!(sn === lastSn + 1 || sn === lastSn && partIndex === this.lastPartIndex + 1 || cc === this.lastCc)) {
21301
+ const partIndex = (_data$part$index = data == null ? void 0 : (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
21302
+ if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
21368
21303
  cea608Parser1.reset();
21369
21304
  cea608Parser2.reset();
21370
21305
  }
@@ -21523,7 +21458,12 @@ class TimelineController {
21523
21458
  this.captionsTracks = {};
21524
21459
  }
21525
21460
  onFragParsingUserdata(event, data) {
21526
- if (!this.enabled || !this.config.enableCEA708Captions) {
21461
+ this.initCea608Parsers();
21462
+ const {
21463
+ cea608Parser1,
21464
+ cea608Parser2
21465
+ } = this;
21466
+ if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
21527
21467
  return;
21528
21468
  }
21529
21469
  const {
@@ -21538,12 +21478,9 @@ class TimelineController {
21538
21478
  for (let i = 0; i < samples.length; i++) {
21539
21479
  const ccBytes = samples[i].bytes;
21540
21480
  if (ccBytes) {
21541
- if (!this.cea608Parser1) {
21542
- this.initCea608Parsers();
21543
- }
21544
21481
  const ccdatas = this.extractCea608Data(ccBytes);
21545
- this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
21546
- this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
21482
+ cea608Parser1.addData(samples[i].pts, ccdatas[0]);
21483
+ cea608Parser2.addData(samples[i].pts, ccdatas[1]);
21547
21484
  }
21548
21485
  }
21549
21486
  }
@@ -21971,104 +21908,12 @@ class EMEController {
21971
21908
  this.mediaKeySessions = [];
21972
21909
  this.keyIdToKeySessionPromise = {};
21973
21910
  this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
21911
+ this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
21912
+ this.onWaitingForKey = this._onWaitingForKey.bind(this);
21974
21913
  this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
21975
21914
  this.log = logger.log.bind(logger, LOGGER_PREFIX);
21976
21915
  this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
21977
21916
  this.error = logger.error.bind(logger, LOGGER_PREFIX);
21978
- this.onMediaEncrypted = event => {
21979
- const {
21980
- initDataType,
21981
- initData
21982
- } = event;
21983
- this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
21984
-
21985
- // Ignore event when initData is null
21986
- if (initData === null) {
21987
- return;
21988
- }
21989
- let keyId;
21990
- let keySystemDomain;
21991
- if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
21992
- // Match sinf keyId to playlist skd://keyId=
21993
- const json = bin2str(new Uint8Array(initData));
21994
- try {
21995
- const sinf = base64Decode(JSON.parse(json).sinf);
21996
- const tenc = parseSinf(new Uint8Array(sinf));
21997
- if (!tenc) {
21998
- return;
21999
- }
22000
- keyId = tenc.subarray(8, 24);
22001
- keySystemDomain = KeySystems.FAIRPLAY;
22002
- } catch (error) {
22003
- this.warn('Failed to parse sinf "encrypted" event message initData');
22004
- return;
22005
- }
22006
- } else {
22007
- // Support clear-lead key-session creation (otherwise depend on playlist keys)
22008
- const psshInfo = parsePssh(initData);
22009
- if (psshInfo === null) {
22010
- return;
22011
- }
22012
- if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22013
- keyId = psshInfo.data.subarray(8, 24);
22014
- }
22015
- keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22016
- }
22017
- if (!keySystemDomain || !keyId) {
22018
- return;
22019
- }
22020
- const keyIdHex = Hex.hexDump(keyId);
22021
- const {
22022
- keyIdToKeySessionPromise,
22023
- mediaKeySessions
22024
- } = this;
22025
- let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22026
- for (let i = 0; i < mediaKeySessions.length; i++) {
22027
- // Match playlist key
22028
- const keyContext = mediaKeySessions[i];
22029
- const decryptdata = keyContext.decryptdata;
22030
- if (decryptdata.pssh || !decryptdata.keyId) {
22031
- continue;
22032
- }
22033
- const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22034
- if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22035
- keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22036
- delete keyIdToKeySessionPromise[oldKeyIdHex];
22037
- decryptdata.pssh = new Uint8Array(initData);
22038
- decryptdata.keyId = keyId;
22039
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22040
- return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22041
- });
22042
- break;
22043
- }
22044
- }
22045
- if (!keySessionContextPromise) {
22046
- // Clear-lead key (not encountered in playlist)
22047
- keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22048
- keySystem,
22049
- mediaKeys
22050
- }) => {
22051
- var _keySystemToKeySystem;
22052
- this.throwIfDestroyed();
22053
- const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22054
- decryptdata.pssh = new Uint8Array(initData);
22055
- decryptdata.keyId = keyId;
22056
- return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22057
- this.throwIfDestroyed();
22058
- const keySessionContext = this.createMediaKeySessionContext({
22059
- decryptdata,
22060
- keySystem,
22061
- mediaKeys
22062
- });
22063
- return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22064
- });
22065
- });
22066
- }
22067
- keySessionContextPromise.catch(error => this.handleError(error));
22068
- };
22069
- this.onWaitingForKey = event => {
22070
- this.log(`"${event.type}" event`);
22071
- };
22072
21917
  this.hls = hls;
22073
21918
  this.config = hls.config;
22074
21919
  this.registerListeners();
@@ -22082,9 +21927,9 @@ class EMEController {
22082
21927
  config.licenseXhrSetup = config.licenseResponseCallback = undefined;
22083
21928
  config.drmSystems = config.drmSystemOptions = {};
22084
21929
  // @ts-ignore
22085
- this.hls = this.config = this.keyIdToKeySessionPromise = null;
21930
+ this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
22086
21931
  // @ts-ignore
22087
- this.onMediaEncrypted = this.onWaitingForKey = null;
21932
+ this.config = null;
22088
21933
  }
22089
21934
  registerListeners() {
22090
21935
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -22348,6 +22193,100 @@ class EMEController {
22348
22193
  }
22349
22194
  return this.attemptKeySystemAccess(keySystemsToAttempt);
22350
22195
  }
22196
+ _onMediaEncrypted(event) {
22197
+ const {
22198
+ initDataType,
22199
+ initData
22200
+ } = event;
22201
+ this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22202
+
22203
+ // Ignore event when initData is null
22204
+ if (initData === null) {
22205
+ return;
22206
+ }
22207
+ let keyId;
22208
+ let keySystemDomain;
22209
+ if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22210
+ // Match sinf keyId to playlist skd://keyId=
22211
+ const json = bin2str(new Uint8Array(initData));
22212
+ try {
22213
+ const sinf = base64Decode(JSON.parse(json).sinf);
22214
+ const tenc = parseSinf(new Uint8Array(sinf));
22215
+ if (!tenc) {
22216
+ return;
22217
+ }
22218
+ keyId = tenc.subarray(8, 24);
22219
+ keySystemDomain = KeySystems.FAIRPLAY;
22220
+ } catch (error) {
22221
+ this.warn('Failed to parse sinf "encrypted" event message initData');
22222
+ return;
22223
+ }
22224
+ } else {
22225
+ // Support clear-lead key-session creation (otherwise depend on playlist keys)
22226
+ const psshInfo = parsePssh(initData);
22227
+ if (psshInfo === null) {
22228
+ return;
22229
+ }
22230
+ if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22231
+ keyId = psshInfo.data.subarray(8, 24);
22232
+ }
22233
+ keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22234
+ }
22235
+ if (!keySystemDomain || !keyId) {
22236
+ return;
22237
+ }
22238
+ const keyIdHex = Hex.hexDump(keyId);
22239
+ const {
22240
+ keyIdToKeySessionPromise,
22241
+ mediaKeySessions
22242
+ } = this;
22243
+ let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22244
+ for (let i = 0; i < mediaKeySessions.length; i++) {
22245
+ // Match playlist key
22246
+ const keyContext = mediaKeySessions[i];
22247
+ const decryptdata = keyContext.decryptdata;
22248
+ if (decryptdata.pssh || !decryptdata.keyId) {
22249
+ continue;
22250
+ }
22251
+ const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22252
+ if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22253
+ keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22254
+ delete keyIdToKeySessionPromise[oldKeyIdHex];
22255
+ decryptdata.pssh = new Uint8Array(initData);
22256
+ decryptdata.keyId = keyId;
22257
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22258
+ return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22259
+ });
22260
+ break;
22261
+ }
22262
+ }
22263
+ if (!keySessionContextPromise) {
22264
+ // Clear-lead key (not encountered in playlist)
22265
+ keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22266
+ keySystem,
22267
+ mediaKeys
22268
+ }) => {
22269
+ var _keySystemToKeySystem;
22270
+ this.throwIfDestroyed();
22271
+ const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22272
+ decryptdata.pssh = new Uint8Array(initData);
22273
+ decryptdata.keyId = keyId;
22274
+ return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22275
+ this.throwIfDestroyed();
22276
+ const keySessionContext = this.createMediaKeySessionContext({
22277
+ decryptdata,
22278
+ keySystem,
22279
+ mediaKeys
22280
+ });
22281
+ return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22282
+ });
22283
+ });
22284
+ }
22285
+ keySessionContextPromise.catch(error => this.handleError(error));
22286
+ }
22287
+ _onWaitingForKey(event) {
22288
+ this.log(`"${event.type}" event`);
22289
+ }
22351
22290
  attemptSetMediaKeys(keySystem, mediaKeys) {
22352
22291
  const queue = this.setMediaKeysQueue.slice();
22353
22292
  this.log(`Setting media-keys for "${keySystem}"`);
@@ -25450,6 +25389,7 @@ function enableStreamingMode(config) {
25450
25389
  }
25451
25390
  }
25452
25391
 
25392
+ let chromeOrFirefox;
25453
25393
  class LevelController extends BasePlaylistController {
25454
25394
  constructor(hls, contentSteeringController) {
25455
25395
  super(hls, '[level-controller]');
@@ -25523,15 +25463,23 @@ class LevelController extends BasePlaylistController {
25523
25463
  let videoCodecFound = false;
25524
25464
  let audioCodecFound = false;
25525
25465
  data.levels.forEach(levelParsed => {
25526
- var _videoCodec;
25466
+ var _audioCodec, _videoCodec;
25527
25467
  const attributes = levelParsed.attrs;
25468
+
25469
+ // erase audio codec info if browser does not support mp4a.40.34.
25470
+ // demuxer will autodetect codec and fallback to mpeg/audio
25528
25471
  let {
25529
25472
  audioCodec,
25530
25473
  videoCodec
25531
25474
  } = levelParsed;
25475
+ if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
25476
+ chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
25477
+ if (chromeOrFirefox) {
25478
+ levelParsed.audioCodec = audioCodec = undefined;
25479
+ }
25480
+ }
25532
25481
  if (audioCodec) {
25533
- // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
25534
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
25482
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
25535
25483
  }
25536
25484
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
25537
25485
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -25656,8 +25604,8 @@ class LevelController extends BasePlaylistController {
25656
25604
  return valueB - valueA;
25657
25605
  }
25658
25606
  }
25659
- if (a.bitrate !== b.bitrate) {
25660
- return a.bitrate - b.bitrate;
25607
+ if (a.averageBitrate !== b.averageBitrate) {
25608
+ return a.averageBitrate - b.averageBitrate;
25661
25609
  }
25662
25610
  return 0;
25663
25611
  });
@@ -26117,8 +26065,6 @@ class KeyLoader {
26117
26065
  }
26118
26066
  return this.loadKeyEME(keyInfo, frag);
26119
26067
  case 'AES-128':
26120
- case 'AES-256':
26121
- case 'AES-256-CTR':
26122
26068
  return this.loadKeyHTTP(keyInfo, frag);
26123
26069
  default:
26124
26070
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -26588,32 +26534,13 @@ class StreamController extends BaseStreamController {
26588
26534
  this.altAudio = false;
26589
26535
  this.audioOnly = false;
26590
26536
  this.fragPlaying = null;
26537
+ this.onvplaying = null;
26538
+ this.onvseeked = null;
26591
26539
  this.fragLastKbps = 0;
26592
26540
  this.couldBacktrack = false;
26593
26541
  this.backtrackFragment = null;
26594
26542
  this.audioCodecSwitch = false;
26595
26543
  this.videoBuffer = null;
26596
- this.onMediaPlaying = () => {
26597
- // tick to speed up FRAG_CHANGED triggering
26598
- this.tick();
26599
- };
26600
- this.onMediaSeeked = () => {
26601
- const media = this.media;
26602
- const currentTime = media ? media.currentTime : null;
26603
- if (isFiniteNumber(currentTime)) {
26604
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
26605
- }
26606
-
26607
- // If seeked was issued before buffer was appended do not tick immediately
26608
- const bufferInfo = this.getMainFwdBufferInfo();
26609
- if (bufferInfo === null || bufferInfo.len === 0) {
26610
- this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
26611
- return;
26612
- }
26613
-
26614
- // tick to speed up FRAG_CHANGED triggering
26615
- this.tick();
26616
- };
26617
26544
  this._registerListeners();
26618
26545
  }
26619
26546
  _registerListeners() {
@@ -26655,8 +26582,6 @@ class StreamController extends BaseStreamController {
26655
26582
  }
26656
26583
  onHandlerDestroying() {
26657
26584
  this._unregisterListeners();
26658
- // @ts-ignore
26659
- this.onMediaPlaying = this.onMediaSeeked = null;
26660
26585
  super.onHandlerDestroying();
26661
26586
  }
26662
26587
  startLoad(startPosition) {
@@ -26983,17 +26908,20 @@ class StreamController extends BaseStreamController {
26983
26908
  onMediaAttached(event, data) {
26984
26909
  super.onMediaAttached(event, data);
26985
26910
  const media = data.media;
26986
- media.addEventListener('playing', this.onMediaPlaying);
26987
- media.addEventListener('seeked', this.onMediaSeeked);
26911
+ this.onvplaying = this.onMediaPlaying.bind(this);
26912
+ this.onvseeked = this.onMediaSeeked.bind(this);
26913
+ media.addEventListener('playing', this.onvplaying);
26914
+ media.addEventListener('seeked', this.onvseeked);
26988
26915
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
26989
26916
  }
26990
26917
  onMediaDetaching() {
26991
26918
  const {
26992
26919
  media
26993
26920
  } = this;
26994
- if (media) {
26995
- media.removeEventListener('playing', this.onMediaPlaying);
26996
- media.removeEventListener('seeked', this.onMediaSeeked);
26921
+ if (media && this.onvplaying && this.onvseeked) {
26922
+ media.removeEventListener('playing', this.onvplaying);
26923
+ media.removeEventListener('seeked', this.onvseeked);
26924
+ this.onvplaying = this.onvseeked = null;
26997
26925
  this.videoBuffer = null;
26998
26926
  }
26999
26927
  this.fragPlaying = null;
@@ -27003,6 +26931,27 @@ class StreamController extends BaseStreamController {
27003
26931
  }
27004
26932
  super.onMediaDetaching();
27005
26933
  }
26934
+ onMediaPlaying() {
26935
+ // tick to speed up FRAG_CHANGED triggering
26936
+ this.tick();
26937
+ }
26938
+ onMediaSeeked() {
26939
+ const media = this.media;
26940
+ const currentTime = media ? media.currentTime : null;
26941
+ if (isFiniteNumber(currentTime)) {
26942
+ this.log(`Media seeked to ${currentTime.toFixed(3)}`);
26943
+ }
26944
+
26945
+ // If seeked was issued before buffer was appended do not tick immediately
26946
+ const bufferInfo = this.getMainFwdBufferInfo();
26947
+ if (bufferInfo === null || bufferInfo.len === 0) {
26948
+ this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
26949
+ return;
26950
+ }
26951
+
26952
+ // tick to speed up FRAG_CHANGED triggering
26953
+ this.tick();
26954
+ }
27006
26955
  onManifestLoading() {
27007
26956
  // reset buffer on manifest loading
27008
26957
  this.log('Trigger BUFFER_RESET');
@@ -27733,7 +27682,7 @@ class Hls {
27733
27682
  * Get the video-dev/hls.js package version.
27734
27683
  */
27735
27684
  static get version() {
27736
- return "1.5.2-0.canary.9923";
27685
+ return "1.5.2";
27737
27686
  }
27738
27687
 
27739
27688
  /**