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
@@ -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
  }
@@ -991,26 +991,6 @@ class LevelDetails {
991
991
  }
992
992
  }
993
993
 
994
- var DecrypterAesMode = {
995
- cbc: 0,
996
- ctr: 1
997
- };
998
-
999
- function isFullSegmentEncryption(method) {
1000
- return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
1001
- }
1002
- function getAesModeFromFullSegmentMethod(method) {
1003
- switch (method) {
1004
- case 'AES-128':
1005
- case 'AES-256':
1006
- return DecrypterAesMode.cbc;
1007
- case 'AES-256-CTR':
1008
- return DecrypterAesMode.ctr;
1009
- default:
1010
- throw new Error(`invalid full segment method ${method}`);
1011
- }
1012
- }
1013
-
1014
994
  // This file is inserted as a shim for modules which we do not want to include into the distro.
1015
995
  // This replacement is done in the "alias" plugin of the rollup config.
1016
996
  var empty = undefined;
@@ -2439,12 +2419,12 @@ class LevelKey {
2439
2419
  this.keyFormatVersions = formatversions;
2440
2420
  this.iv = iv;
2441
2421
  this.encrypted = method ? method !== 'NONE' : false;
2442
- this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
2422
+ this.isCommonEncryption = this.encrypted && method !== 'AES-128';
2443
2423
  }
2444
2424
  isSupported() {
2445
2425
  // If it's Segment encryption or No encryption, just select that key system
2446
2426
  if (this.method) {
2447
- if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
2427
+ if (this.method === 'AES-128' || this.method === 'NONE') {
2448
2428
  return true;
2449
2429
  }
2450
2430
  if (this.keyFormat === 'identity') {
@@ -2458,13 +2438,14 @@ class LevelKey {
2458
2438
  if (!this.encrypted || !this.uri) {
2459
2439
  return null;
2460
2440
  }
2461
- if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
2441
+ if (this.method === 'AES-128' && this.uri && !this.iv) {
2462
2442
  if (typeof sn !== 'number') {
2463
2443
  // We are fetching decryption data for a initialization segment
2464
- // If the segment was encrypted with AES-128/256
2444
+ // If the segment was encrypted with AES-128
2465
2445
  // It must have an IV defined. We cannot substitute the Segment Number in.
2466
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2467
-
2446
+ if (this.method === 'AES-128' && !this.iv) {
2447
+ logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2448
+ }
2468
2449
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
2469
2450
  sn = 0;
2470
2451
  }
@@ -2611,28 +2592,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
2611
2592
  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
2612
2593
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
2613
2594
  }
2595
+
2596
+ // Idealy fLaC and Opus would be first (spec-compliant) but
2597
+ // some browsers will report that fLaC is supported then fail.
2598
+ // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
2614
2599
  const codecsToCheck = {
2615
- // Idealy fLaC and Opus would be first (spec-compliant) but
2616
- // some browsers will report that fLaC is supported then fail.
2617
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
2618
2600
  flac: ['flac', 'fLaC', 'FLAC'],
2619
- opus: ['opus', 'Opus'],
2620
- // Replace audio codec info if browser does not support mp4a.40.34,
2621
- // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
2622
- 'mp4a.40.34': ['mp3']
2601
+ opus: ['opus', 'Opus']
2623
2602
  }[lowerCaseCodec];
2624
2603
  for (let i = 0; i < codecsToCheck.length; i++) {
2625
- var _getMediaSource;
2626
2604
  if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
2627
2605
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
2628
2606
  return codecsToCheck[i];
2629
- } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
2630
- return '';
2631
2607
  }
2632
2608
  }
2633
2609
  return lowerCaseCodec;
2634
2610
  }
2635
- const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
2611
+ const AUDIO_CODEC_REGEXP = /flac|opus/i;
2636
2612
  function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
2637
2613
  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
2638
2614
  }
@@ -2655,16 +2631,6 @@ function convertAVC1ToAVCOTI(codec) {
2655
2631
  }
2656
2632
  return codec;
2657
2633
  }
2658
- function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
2659
- const MediaSource = getMediaSource(preferManagedMediaSource) || {
2660
- isTypeSupported: () => false
2661
- };
2662
- return {
2663
- mpeg: MediaSource.isTypeSupported('audio/mpeg'),
2664
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
2665
- ac3: false
2666
- };
2667
- }
2668
2634
 
2669
2635
  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;
2670
2636
  const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -4242,47 +4208,7 @@ class LatencyController {
4242
4208
  this.currentTime = 0;
4243
4209
  this.stallCount = 0;
4244
4210
  this._latency = null;
4245
- this.onTimeupdate = () => {
4246
- const {
4247
- media,
4248
- levelDetails
4249
- } = this;
4250
- if (!media || !levelDetails) {
4251
- return;
4252
- }
4253
- this.currentTime = media.currentTime;
4254
- const latency = this.computeLatency();
4255
- if (latency === null) {
4256
- return;
4257
- }
4258
- this._latency = latency;
4259
-
4260
- // Adapt playbackRate to meet target latency in low-latency mode
4261
- const {
4262
- lowLatencyMode,
4263
- maxLiveSyncPlaybackRate
4264
- } = this.config;
4265
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4266
- return;
4267
- }
4268
- const targetLatency = this.targetLatency;
4269
- if (targetLatency === null) {
4270
- return;
4271
- }
4272
- const distanceFromTarget = latency - targetLatency;
4273
- // Only adjust playbackRate when within one target duration of targetLatency
4274
- // and more than one second from under-buffering.
4275
- // Playback further than one target duration from target can be considered DVR playback.
4276
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4277
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4278
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4279
- const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4280
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4281
- media.playbackRate = Math.min(max, Math.max(1, rate));
4282
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4283
- media.playbackRate = 1;
4284
- }
4285
- };
4211
+ this.timeupdateHandler = () => this.timeupdate();
4286
4212
  this.hls = hls;
4287
4213
  this.config = hls.config;
4288
4214
  this.registerListeners();
@@ -4374,7 +4300,7 @@ class LatencyController {
4374
4300
  this.onMediaDetaching();
4375
4301
  this.levelDetails = null;
4376
4302
  // @ts-ignore
4377
- this.hls = this.onTimeupdate = null;
4303
+ this.hls = this.timeupdateHandler = null;
4378
4304
  }
4379
4305
  registerListeners() {
4380
4306
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -4392,11 +4318,11 @@ class LatencyController {
4392
4318
  }
4393
4319
  onMediaAttached(event, data) {
4394
4320
  this.media = data.media;
4395
- this.media.addEventListener('timeupdate', this.onTimeupdate);
4321
+ this.media.addEventListener('timeupdate', this.timeupdateHandler);
4396
4322
  }
4397
4323
  onMediaDetaching() {
4398
4324
  if (this.media) {
4399
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4325
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4400
4326
  this.media = null;
4401
4327
  }
4402
4328
  }
@@ -4410,10 +4336,10 @@ class LatencyController {
4410
4336
  }) {
4411
4337
  this.levelDetails = details;
4412
4338
  if (details.advanced) {
4413
- this.onTimeupdate();
4339
+ this.timeupdate();
4414
4340
  }
4415
4341
  if (!details.live && this.media) {
4416
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4342
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4417
4343
  }
4418
4344
  }
4419
4345
  onError(event, data) {
@@ -4426,6 +4352,47 @@ class LatencyController {
4426
4352
  logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
4427
4353
  }
4428
4354
  }
4355
+ timeupdate() {
4356
+ const {
4357
+ media,
4358
+ levelDetails
4359
+ } = this;
4360
+ if (!media || !levelDetails) {
4361
+ return;
4362
+ }
4363
+ this.currentTime = media.currentTime;
4364
+ const latency = this.computeLatency();
4365
+ if (latency === null) {
4366
+ return;
4367
+ }
4368
+ this._latency = latency;
4369
+
4370
+ // Adapt playbackRate to meet target latency in low-latency mode
4371
+ const {
4372
+ lowLatencyMode,
4373
+ maxLiveSyncPlaybackRate
4374
+ } = this.config;
4375
+ if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4376
+ return;
4377
+ }
4378
+ const targetLatency = this.targetLatency;
4379
+ if (targetLatency === null) {
4380
+ return;
4381
+ }
4382
+ const distanceFromTarget = latency - targetLatency;
4383
+ // Only adjust playbackRate when within one target duration of targetLatency
4384
+ // and more than one second from under-buffering.
4385
+ // Playback further than one target duration from target can be considered DVR playback.
4386
+ const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4387
+ const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4388
+ if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4389
+ const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4390
+ const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4391
+ media.playbackRate = Math.min(max, Math.max(1, rate));
4392
+ } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4393
+ media.playbackRate = 1;
4394
+ }
4395
+ }
4429
4396
  estimateLiveEdge() {
4430
4397
  const {
4431
4398
  levelDetails
@@ -6213,7 +6180,7 @@ class AbrController {
6213
6180
  const bwEstimate = this.getBwEstimate();
6214
6181
  const levels = hls.levels;
6215
6182
  const level = levels[frag.level];
6216
- const expectedLen = stats.total || Math.max(stats.loaded, Math.round(duration * level.maxBitrate / 8));
6183
+ const expectedLen = stats.total || Math.max(stats.loaded, Math.round(duration * level.averageBitrate / 8));
6217
6184
  let timeStreaming = loadedFirstByte ? timeLoading - ttfb : timeLoading;
6218
6185
  if (timeStreaming < 1 && loadedFirstByte) {
6219
6186
  timeStreaming = Math.min(timeLoading, stats.loaded * 8 / bwEstimate);
@@ -6256,7 +6223,7 @@ class AbrController {
6256
6223
  // If there has been no loading progress, sample TTFB
6257
6224
  this.bwEstimator.sampleTTFB(timeLoading);
6258
6225
  }
6259
- const nextLoadLevelBitrate = levels[nextLoadLevel].bitrate;
6226
+ const nextLoadLevelBitrate = levels[nextLoadLevel].maxBitrate;
6260
6227
  if (this.getBwEstimate() * this.hls.config.abrBandWidthUpFactor > nextLoadLevelBitrate) {
6261
6228
  this.resetEstimator(nextLoadLevelBitrate);
6262
6229
  }
@@ -7053,12 +7020,6 @@ class BufferController {
7053
7020
  this.lastMpegAudioChunk = null;
7054
7021
  // @ts-ignore
7055
7022
  this.hls = null;
7056
- // @ts-ignore
7057
- this._onMediaSourceOpen = this._onMediaSourceClose = null;
7058
- // @ts-ignore
7059
- this._onMediaSourceEnded = null;
7060
- // @ts-ignore
7061
- this._onStartStreaming = this._onEndStreaming = null;
7062
7023
  }
7063
7024
  registerListeners() {
7064
7025
  const {
@@ -9611,6 +9572,7 @@ function enableStreamingMode(config) {
9611
9572
  }
9612
9573
  }
9613
9574
 
9575
+ let chromeOrFirefox;
9614
9576
  class LevelController extends BasePlaylistController {
9615
9577
  constructor(hls, contentSteeringController) {
9616
9578
  super(hls, '[level-controller]');
@@ -9684,15 +9646,23 @@ class LevelController extends BasePlaylistController {
9684
9646
  let videoCodecFound = false;
9685
9647
  let audioCodecFound = false;
9686
9648
  data.levels.forEach(levelParsed => {
9687
- var _videoCodec;
9649
+ var _audioCodec, _videoCodec;
9688
9650
  const attributes = levelParsed.attrs;
9651
+
9652
+ // erase audio codec info if browser does not support mp4a.40.34.
9653
+ // demuxer will autodetect codec and fallback to mpeg/audio
9689
9654
  let {
9690
9655
  audioCodec,
9691
9656
  videoCodec
9692
9657
  } = levelParsed;
9658
+ if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
9659
+ chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
9660
+ if (chromeOrFirefox) {
9661
+ levelParsed.audioCodec = audioCodec = undefined;
9662
+ }
9663
+ }
9693
9664
  if (audioCodec) {
9694
- // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
9695
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
9665
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
9696
9666
  }
9697
9667
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
9698
9668
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -9817,8 +9787,8 @@ class LevelController extends BasePlaylistController {
9817
9787
  return valueB - valueA;
9818
9788
  }
9819
9789
  }
9820
- if (a.bitrate !== b.bitrate) {
9821
- return a.bitrate - b.bitrate;
9790
+ if (a.averageBitrate !== b.averageBitrate) {
9791
+ return a.averageBitrate - b.averageBitrate;
9822
9792
  }
9823
9793
  return 0;
9824
9794
  });
@@ -10820,8 +10790,8 @@ function createLoaderContext(frag, part = null) {
10820
10790
  var _frag$decryptdata;
10821
10791
  let byteRangeStart = start;
10822
10792
  let byteRangeEnd = end;
10823
- if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
10824
- // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
10793
+ if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
10794
+ // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
10825
10795
  // has the unencrypted size specified in the range.
10826
10796
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
10827
10797
  const fragmentLen = end - start;
@@ -10854,9 +10824,6 @@ function createGapLoadError(frag, part) {
10854
10824
  (part ? part : frag).stats.aborted = true;
10855
10825
  return new LoadError(errorData);
10856
10826
  }
10857
- function isMethodFullSegmentAesCbc(method) {
10858
- return method === 'AES-128' || method === 'AES-256';
10859
- }
10860
10827
  class LoadError extends Error {
10861
10828
  constructor(data) {
10862
10829
  super(data.error.message);
@@ -11002,8 +10969,6 @@ class KeyLoader {
11002
10969
  }
11003
10970
  return this.loadKeyEME(keyInfo, frag);
11004
10971
  case 'AES-128':
11005
- case 'AES-256':
11006
- case 'AES-256-CTR':
11007
10972
  return this.loadKeyHTTP(keyInfo, frag);
11008
10973
  default:
11009
10974
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -11408,61 +11373,33 @@ function alignMediaPlaylistByPDT(details, refDetails) {
11408
11373
  }
11409
11374
 
11410
11375
  class AESCrypto {
11411
- constructor(subtle, iv, aesMode) {
11376
+ constructor(subtle, iv) {
11412
11377
  this.subtle = void 0;
11413
11378
  this.aesIV = void 0;
11414
- this.aesMode = void 0;
11415
11379
  this.subtle = subtle;
11416
11380
  this.aesIV = iv;
11417
- this.aesMode = aesMode;
11418
11381
  }
11419
11382
  decrypt(data, key) {
11420
- switch (this.aesMode) {
11421
- case DecrypterAesMode.cbc:
11422
- return this.subtle.decrypt({
11423
- name: 'AES-CBC',
11424
- iv: this.aesIV
11425
- }, key, data);
11426
- case DecrypterAesMode.ctr:
11427
- return this.subtle.decrypt({
11428
- name: 'AES-CTR',
11429
- counter: this.aesIV,
11430
- length: 64
11431
- },
11432
- //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
11433
- key, data);
11434
- default:
11435
- throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
11436
- }
11383
+ return this.subtle.decrypt({
11384
+ name: 'AES-CBC',
11385
+ iv: this.aesIV
11386
+ }, key, data);
11437
11387
  }
11438
11388
  }
11439
11389
 
11440
11390
  class FastAESKey {
11441
- constructor(subtle, key, aesMode) {
11391
+ constructor(subtle, key) {
11442
11392
  this.subtle = void 0;
11443
11393
  this.key = void 0;
11444
- this.aesMode = void 0;
11445
11394
  this.subtle = subtle;
11446
11395
  this.key = key;
11447
- this.aesMode = aesMode;
11448
11396
  }
11449
11397
  expandKey() {
11450
- const subtleAlgoName = getSubtleAlgoName(this.aesMode);
11451
11398
  return this.subtle.importKey('raw', this.key, {
11452
- name: subtleAlgoName
11399
+ name: 'AES-CBC'
11453
11400
  }, false, ['encrypt', 'decrypt']);
11454
11401
  }
11455
11402
  }
11456
- function getSubtleAlgoName(aesMode) {
11457
- switch (aesMode) {
11458
- case DecrypterAesMode.cbc:
11459
- return 'AES-CBC';
11460
- case DecrypterAesMode.ctr:
11461
- return 'AES-CTR';
11462
- default:
11463
- throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
11464
- }
11465
- }
11466
11403
 
11467
11404
  // PKCS7
11468
11405
  function removePadding(array) {
@@ -11712,8 +11649,7 @@ class Decrypter {
11712
11649
  this.currentIV = null;
11713
11650
  this.currentResult = null;
11714
11651
  this.useSoftware = void 0;
11715
- this.enableSoftwareAES = void 0;
11716
- this.enableSoftwareAES = config.enableSoftwareAES;
11652
+ this.useSoftware = config.enableSoftwareAES;
11717
11653
  this.removePKCS7Padding = removePKCS7Padding;
11718
11654
  // built in decryptor expects PKCS7 padding
11719
11655
  if (removePKCS7Padding) {
@@ -11726,7 +11662,9 @@ class Decrypter {
11726
11662
  /* no-op */
11727
11663
  }
11728
11664
  }
11729
- this.useSoftware = this.subtle === null;
11665
+ if (this.subtle === null) {
11666
+ this.useSoftware = true;
11667
+ }
11730
11668
  }
11731
11669
  destroy() {
11732
11670
  this.subtle = null;
@@ -11764,10 +11702,10 @@ class Decrypter {
11764
11702
  this.softwareDecrypter = null;
11765
11703
  }
11766
11704
  }
11767
- decrypt(data, key, iv, aesMode) {
11705
+ decrypt(data, key, iv) {
11768
11706
  if (this.useSoftware) {
11769
11707
  return new Promise((resolve, reject) => {
11770
- this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
11708
+ this.softwareDecrypt(new Uint8Array(data), key, iv);
11771
11709
  const decryptResult = this.flush();
11772
11710
  if (decryptResult) {
11773
11711
  resolve(decryptResult.buffer);
@@ -11776,21 +11714,17 @@ class Decrypter {
11776
11714
  }
11777
11715
  });
11778
11716
  }
11779
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
11717
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
11780
11718
  }
11781
11719
 
11782
11720
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
11783
11721
  // data is handled in the flush() call
11784
- softwareDecrypt(data, key, iv, aesMode) {
11722
+ softwareDecrypt(data, key, iv) {
11785
11723
  const {
11786
11724
  currentIV,
11787
11725
  currentResult,
11788
11726
  remainderData
11789
11727
  } = this;
11790
- if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
11791
- logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
11792
- return null;
11793
- }
11794
11728
  this.logOnce('JS AES decrypt');
11795
11729
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
11796
11730
  // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
@@ -11823,11 +11757,11 @@ class Decrypter {
11823
11757
  }
11824
11758
  return result;
11825
11759
  }
11826
- webCryptoDecrypt(data, key, iv, aesMode) {
11760
+ webCryptoDecrypt(data, key, iv) {
11827
11761
  const subtle = this.subtle;
11828
11762
  if (this.key !== key || !this.fastAesKey) {
11829
11763
  this.key = key;
11830
- this.fastAesKey = new FastAESKey(subtle, key, aesMode);
11764
+ this.fastAesKey = new FastAESKey(subtle, key);
11831
11765
  }
11832
11766
  return this.fastAesKey.expandKey().then(aesKey => {
11833
11767
  // decrypt using web crypto
@@ -11835,25 +11769,22 @@ class Decrypter {
11835
11769
  return Promise.reject(new Error('web crypto not initialized'));
11836
11770
  }
11837
11771
  this.logOnce('WebCrypto AES decrypt');
11838
- const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
11772
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv));
11839
11773
  return crypto.decrypt(data.buffer, aesKey);
11840
11774
  }).catch(err => {
11841
11775
  logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
11842
- return this.onWebCryptoError(data, key, iv, aesMode);
11776
+ return this.onWebCryptoError(data, key, iv);
11843
11777
  });
11844
11778
  }
11845
- onWebCryptoError(data, key, iv, aesMode) {
11846
- const enableSoftwareAES = this.enableSoftwareAES;
11847
- if (enableSoftwareAES) {
11848
- this.useSoftware = true;
11849
- this.logEnabled = true;
11850
- this.softwareDecrypt(data, key, iv, aesMode);
11851
- const decryptResult = this.flush();
11852
- if (decryptResult) {
11853
- return decryptResult.buffer;
11854
- }
11779
+ onWebCryptoError(data, key, iv) {
11780
+ this.useSoftware = true;
11781
+ this.logEnabled = true;
11782
+ this.softwareDecrypt(data, key, iv);
11783
+ const decryptResult = this.flush();
11784
+ if (decryptResult) {
11785
+ return decryptResult.buffer;
11855
11786
  }
11856
- throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
11787
+ throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
11857
11788
  }
11858
11789
  getValidChunk(data) {
11859
11790
  let currentChunk = data;
@@ -11929,59 +11860,11 @@ class BaseStreamController extends TaskLoop {
11929
11860
  this.startFragRequested = false;
11930
11861
  this.decrypter = void 0;
11931
11862
  this.initPTS = [];
11863
+ this.onvseeking = null;
11864
+ this.onvended = null;
11932
11865
  this.logPrefix = '';
11933
11866
  this.log = void 0;
11934
11867
  this.warn = void 0;
11935
- this.onMediaSeeking = () => {
11936
- const {
11937
- config,
11938
- fragCurrent,
11939
- media,
11940
- mediaBuffer,
11941
- state
11942
- } = this;
11943
- const currentTime = media ? media.currentTime : 0;
11944
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
11945
- this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
11946
- if (this.state === State.ENDED) {
11947
- this.resetLoadingState();
11948
- } else if (fragCurrent) {
11949
- // Seeking while frag load is in progress
11950
- const tolerance = config.maxFragLookUpTolerance;
11951
- const fragStartOffset = fragCurrent.start - tolerance;
11952
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
11953
- // if seeking out of buffered range or into new one
11954
- if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
11955
- const pastFragment = currentTime > fragEndOffset;
11956
- // if the seek position is outside the current fragment range
11957
- if (currentTime < fragStartOffset || pastFragment) {
11958
- if (pastFragment && fragCurrent.loader) {
11959
- this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
11960
- fragCurrent.abortRequests();
11961
- this.resetLoadingState();
11962
- }
11963
- this.fragPrevious = null;
11964
- }
11965
- }
11966
- }
11967
- if (media) {
11968
- // Remove gap fragments
11969
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
11970
- this.lastCurrentTime = currentTime;
11971
- }
11972
-
11973
- // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
11974
- if (!this.loadedmetadata && !bufferInfo.len) {
11975
- this.nextLoadPosition = this.startPosition = currentTime;
11976
- }
11977
-
11978
- // Async tick to speed up processing
11979
- this.tickImmediate();
11980
- };
11981
- this.onMediaEnded = () => {
11982
- // reset startPosition and lastCurrentTime to restart playback @ stream beginning
11983
- this.startPosition = this.lastCurrentTime = 0;
11984
- };
11985
11868
  this.playlistType = playlistType;
11986
11869
  this.logPrefix = logPrefix;
11987
11870
  this.log = logger.log.bind(logger, `${logPrefix}:`);
@@ -12046,8 +11929,10 @@ class BaseStreamController extends TaskLoop {
12046
11929
  }
12047
11930
  onMediaAttached(event, data) {
12048
11931
  const media = this.media = this.mediaBuffer = data.media;
12049
- media.addEventListener('seeking', this.onMediaSeeking);
12050
- media.addEventListener('ended', this.onMediaEnded);
11932
+ this.onvseeking = this.onMediaSeeking.bind(this);
11933
+ this.onvended = this.onMediaEnded.bind(this);
11934
+ media.addEventListener('seeking', this.onvseeking);
11935
+ media.addEventListener('ended', this.onvended);
12051
11936
  const config = this.config;
12052
11937
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
12053
11938
  this.startLoad(config.startPosition);
@@ -12061,9 +11946,10 @@ class BaseStreamController extends TaskLoop {
12061
11946
  }
12062
11947
 
12063
11948
  // remove video listeners
12064
- if (media) {
12065
- media.removeEventListener('seeking', this.onMediaSeeking);
12066
- media.removeEventListener('ended', this.onMediaEnded);
11949
+ if (media && this.onvseeking && this.onvended) {
11950
+ media.removeEventListener('seeking', this.onvseeking);
11951
+ media.removeEventListener('ended', this.onvended);
11952
+ this.onvseeking = this.onvended = null;
12067
11953
  }
12068
11954
  if (this.keyLoader) {
12069
11955
  this.keyLoader.detach();
@@ -12073,6 +11959,56 @@ class BaseStreamController extends TaskLoop {
12073
11959
  this.fragmentTracker.removeAllFragments();
12074
11960
  this.stopLoad();
12075
11961
  }
11962
+ onMediaSeeking() {
11963
+ const {
11964
+ config,
11965
+ fragCurrent,
11966
+ media,
11967
+ mediaBuffer,
11968
+ state
11969
+ } = this;
11970
+ const currentTime = media ? media.currentTime : 0;
11971
+ const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
11972
+ this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
11973
+ if (this.state === State.ENDED) {
11974
+ this.resetLoadingState();
11975
+ } else if (fragCurrent) {
11976
+ // Seeking while frag load is in progress
11977
+ const tolerance = config.maxFragLookUpTolerance;
11978
+ const fragStartOffset = fragCurrent.start - tolerance;
11979
+ const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
11980
+ // if seeking out of buffered range or into new one
11981
+ if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
11982
+ const pastFragment = currentTime > fragEndOffset;
11983
+ // if the seek position is outside the current fragment range
11984
+ if (currentTime < fragStartOffset || pastFragment) {
11985
+ if (pastFragment && fragCurrent.loader) {
11986
+ this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
11987
+ fragCurrent.abortRequests();
11988
+ this.resetLoadingState();
11989
+ }
11990
+ this.fragPrevious = null;
11991
+ }
11992
+ }
11993
+ }
11994
+ if (media) {
11995
+ // Remove gap fragments
11996
+ this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
11997
+ this.lastCurrentTime = currentTime;
11998
+ }
11999
+
12000
+ // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
12001
+ if (!this.loadedmetadata && !bufferInfo.len) {
12002
+ this.nextLoadPosition = this.startPosition = currentTime;
12003
+ }
12004
+
12005
+ // Async tick to speed up processing
12006
+ this.tickImmediate();
12007
+ }
12008
+ onMediaEnded() {
12009
+ // reset startPosition and lastCurrentTime to restart playback @ stream beginning
12010
+ this.startPosition = this.lastCurrentTime = 0;
12011
+ }
12076
12012
  onManifestLoaded(event, data) {
12077
12013
  this.startTimeOffset = data.startTimeOffset;
12078
12014
  this.initPTS = [];
@@ -12082,7 +12018,7 @@ class BaseStreamController extends TaskLoop {
12082
12018
  this.stopLoad();
12083
12019
  super.onHandlerDestroying();
12084
12020
  // @ts-ignore
12085
- this.hls = this.onMediaSeeking = this.onMediaEnded = null;
12021
+ this.hls = null;
12086
12022
  }
12087
12023
  onHandlerDestroyed() {
12088
12024
  this.state = State.STOPPED;
@@ -12213,10 +12149,10 @@ class BaseStreamController extends TaskLoop {
12213
12149
  const decryptData = frag.decryptdata;
12214
12150
 
12215
12151
  // check to see if the payload needs to be decrypted
12216
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
12152
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
12217
12153
  const startTime = self.performance.now();
12218
12154
  // decrypt init segment data
12219
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
12155
+ return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
12220
12156
  hls.trigger(Events.ERROR, {
12221
12157
  type: ErrorTypes.MEDIA_ERROR,
12222
12158
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -13368,7 +13304,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
13368
13304
  */
13369
13305
  function getAudioConfig(observer, data, offset, audioCodec) {
13370
13306
  let adtsObjectType;
13371
- let originalAdtsObjectType;
13372
13307
  let adtsExtensionSamplingIndex;
13373
13308
  let adtsChannelConfig;
13374
13309
  let config;
@@ -13376,7 +13311,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13376
13311
  const manifestCodec = audioCodec;
13377
13312
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
13378
13313
  // byte 2
13379
- adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
13314
+ adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
13380
13315
  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
13381
13316
  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
13382
13317
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
@@ -13393,8 +13328,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13393
13328
  // byte 3
13394
13329
  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
13395
13330
  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
13396
- // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
13397
- if (/firefox|palemoon/i.test(userAgent)) {
13331
+ // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
13332
+ if (/firefox/i.test(userAgent)) {
13398
13333
  if (adtsSamplingIndex >= 6) {
13399
13334
  adtsObjectType = 5;
13400
13335
  config = new Array(4);
@@ -13488,7 +13423,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13488
13423
  samplerate: adtsSamplingRates[adtsSamplingIndex],
13489
13424
  channelCount: adtsChannelConfig,
13490
13425
  codec: 'mp4a.40.' + adtsObjectType,
13491
- parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
13492
13426
  manifestCodec
13493
13427
  };
13494
13428
  }
@@ -13543,8 +13477,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
13543
13477
  track.channelCount = config.channelCount;
13544
13478
  track.codec = config.codec;
13545
13479
  track.manifestCodec = config.manifestCodec;
13546
- track.parsedCodec = config.parsedCodec;
13547
- logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
13480
+ logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
13548
13481
  }
13549
13482
  }
13550
13483
  function getFrameDuration(samplerate) {
@@ -14607,7 +14540,7 @@ class SampleAesDecrypter {
14607
14540
  });
14608
14541
  }
14609
14542
  decryptBuffer(encryptedData) {
14610
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
14543
+ return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
14611
14544
  }
14612
14545
 
14613
14546
  // AAC - encrypt all full 16 bytes blocks starting from offset 16
@@ -16810,7 +16743,7 @@ class MP4Remuxer {
16810
16743
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
16811
16744
  for (let j = 0; j < missing; j++) {
16812
16745
  const newStamp = Math.max(nextPts, 0);
16813
- let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
16746
+ let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
16814
16747
  if (!fillFrame) {
16815
16748
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
16816
16749
  fillFrame = sample.unit.subarray();
@@ -16938,7 +16871,7 @@ class MP4Remuxer {
16938
16871
  // samples count of this segment's duration
16939
16872
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
16940
16873
  // silent frame
16941
- const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
16874
+ const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
16942
16875
  logger.warn('[mp4-remuxer]: remux empty Audio');
16943
16876
  // Can't remux if we can't generate a silent frame...
16944
16877
  if (!silentFrame) {
@@ -17329,15 +17262,13 @@ class Transmuxer {
17329
17262
  initSegmentData
17330
17263
  } = transmuxConfig;
17331
17264
  const keyData = getEncryptionType(uintData, decryptdata);
17332
- if (keyData && isFullSegmentEncryption(keyData.method)) {
17265
+ if (keyData && keyData.method === 'AES-128') {
17333
17266
  const decrypter = this.getDecrypter();
17334
- const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
17335
-
17336
17267
  // Software decryption is synchronous; webCrypto is not
17337
17268
  if (decrypter.isSync()) {
17338
17269
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
17339
17270
  // data is handled in the flush() call
17340
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
17271
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
17341
17272
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
17342
17273
  const loadingParts = chunkMeta.part > -1;
17343
17274
  if (loadingParts) {
@@ -17349,7 +17280,7 @@ class Transmuxer {
17349
17280
  }
17350
17281
  uintData = new Uint8Array(decryptedData);
17351
17282
  } else {
17352
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
17283
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
17353
17284
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
17354
17285
  // the decrypted data has been transmuxed
17355
17286
  const result = this.push(decryptedData, null, chunkMeta);
@@ -18003,7 +17934,14 @@ class TransmuxerInterface {
18003
17934
  this.observer = new EventEmitter();
18004
17935
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
18005
17936
  this.observer.on(Events.ERROR, forwardMessage);
18006
- const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
17937
+ const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
17938
+ isTypeSupported: () => false
17939
+ };
17940
+ const m2tsTypeSupported = {
17941
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
17942
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
17943
+ ac3: false
17944
+ };
18007
17945
 
18008
17946
  // navigator.vendor is not always available in Web Worker
18009
17947
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -18599,32 +18537,13 @@ class StreamController extends BaseStreamController {
18599
18537
  this.altAudio = false;
18600
18538
  this.audioOnly = false;
18601
18539
  this.fragPlaying = null;
18540
+ this.onvplaying = null;
18541
+ this.onvseeked = null;
18602
18542
  this.fragLastKbps = 0;
18603
18543
  this.couldBacktrack = false;
18604
18544
  this.backtrackFragment = null;
18605
18545
  this.audioCodecSwitch = false;
18606
18546
  this.videoBuffer = null;
18607
- this.onMediaPlaying = () => {
18608
- // tick to speed up FRAG_CHANGED triggering
18609
- this.tick();
18610
- };
18611
- this.onMediaSeeked = () => {
18612
- const media = this.media;
18613
- const currentTime = media ? media.currentTime : null;
18614
- if (isFiniteNumber(currentTime)) {
18615
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
18616
- }
18617
-
18618
- // If seeked was issued before buffer was appended do not tick immediately
18619
- const bufferInfo = this.getMainFwdBufferInfo();
18620
- if (bufferInfo === null || bufferInfo.len === 0) {
18621
- this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
18622
- return;
18623
- }
18624
-
18625
- // tick to speed up FRAG_CHANGED triggering
18626
- this.tick();
18627
- };
18628
18547
  this._registerListeners();
18629
18548
  }
18630
18549
  _registerListeners() {
@@ -18666,8 +18585,6 @@ class StreamController extends BaseStreamController {
18666
18585
  }
18667
18586
  onHandlerDestroying() {
18668
18587
  this._unregisterListeners();
18669
- // @ts-ignore
18670
- this.onMediaPlaying = this.onMediaSeeked = null;
18671
18588
  super.onHandlerDestroying();
18672
18589
  }
18673
18590
  startLoad(startPosition) {
@@ -18994,17 +18911,20 @@ class StreamController extends BaseStreamController {
18994
18911
  onMediaAttached(event, data) {
18995
18912
  super.onMediaAttached(event, data);
18996
18913
  const media = data.media;
18997
- media.addEventListener('playing', this.onMediaPlaying);
18998
- media.addEventListener('seeked', this.onMediaSeeked);
18914
+ this.onvplaying = this.onMediaPlaying.bind(this);
18915
+ this.onvseeked = this.onMediaSeeked.bind(this);
18916
+ media.addEventListener('playing', this.onvplaying);
18917
+ media.addEventListener('seeked', this.onvseeked);
18999
18918
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
19000
18919
  }
19001
18920
  onMediaDetaching() {
19002
18921
  const {
19003
18922
  media
19004
18923
  } = this;
19005
- if (media) {
19006
- media.removeEventListener('playing', this.onMediaPlaying);
19007
- media.removeEventListener('seeked', this.onMediaSeeked);
18924
+ if (media && this.onvplaying && this.onvseeked) {
18925
+ media.removeEventListener('playing', this.onvplaying);
18926
+ media.removeEventListener('seeked', this.onvseeked);
18927
+ this.onvplaying = this.onvseeked = null;
19008
18928
  this.videoBuffer = null;
19009
18929
  }
19010
18930
  this.fragPlaying = null;
@@ -19014,6 +18934,27 @@ class StreamController extends BaseStreamController {
19014
18934
  }
19015
18935
  super.onMediaDetaching();
19016
18936
  }
18937
+ onMediaPlaying() {
18938
+ // tick to speed up FRAG_CHANGED triggering
18939
+ this.tick();
18940
+ }
18941
+ onMediaSeeked() {
18942
+ const media = this.media;
18943
+ const currentTime = media ? media.currentTime : null;
18944
+ if (isFiniteNumber(currentTime)) {
18945
+ this.log(`Media seeked to ${currentTime.toFixed(3)}`);
18946
+ }
18947
+
18948
+ // If seeked was issued before buffer was appended do not tick immediately
18949
+ const bufferInfo = this.getMainFwdBufferInfo();
18950
+ if (bufferInfo === null || bufferInfo.len === 0) {
18951
+ this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
18952
+ return;
18953
+ }
18954
+
18955
+ // tick to speed up FRAG_CHANGED triggering
18956
+ this.tick();
18957
+ }
19017
18958
  onManifestLoading() {
19018
18959
  // reset buffer on manifest loading
19019
18960
  this.log('Trigger BUFFER_RESET');
@@ -19744,7 +19685,7 @@ class Hls {
19744
19685
  * Get the video-dev/hls.js package version.
19745
19686
  */
19746
19687
  static get version() {
19747
- return "1.5.2-0.canary.9923";
19688
+ return "1.5.2";
19748
19689
  }
19749
19690
 
19750
19691
  /**