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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +1 -2
  2. package/dist/hls-demo.js +0 -10
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +1283 -2293
  5. package/dist/hls.js.d.ts +84 -97
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1030 -1435
  8. package/dist/hls.light.js.map +1 -1
  9. package/dist/hls.light.min.js +1 -1
  10. package/dist/hls.light.min.js.map +1 -1
  11. package/dist/hls.light.mjs +809 -1209
  12. package/dist/hls.light.mjs.map +1 -1
  13. package/dist/hls.min.js +1 -1
  14. package/dist/hls.min.js.map +1 -1
  15. package/dist/hls.mjs +1039 -2030
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +22 -22
  20. package/src/config.ts +2 -3
  21. package/src/controller/abr-controller.ts +20 -24
  22. package/src/controller/audio-stream-controller.ts +74 -68
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +8 -20
  25. package/src/controller/base-stream-controller.ts +36 -157
  26. package/src/controller/buffer-controller.ts +99 -226
  27. package/src/controller/buffer-operation-queue.ts +19 -16
  28. package/src/controller/cap-level-controller.ts +2 -2
  29. package/src/controller/cmcd-controller.ts +6 -27
  30. package/src/controller/content-steering-controller.ts +6 -8
  31. package/src/controller/eme-controller.ts +22 -9
  32. package/src/controller/error-controller.ts +8 -6
  33. package/src/controller/fps-controller.ts +3 -2
  34. package/src/controller/fragment-tracker.ts +11 -15
  35. package/src/controller/gap-controller.ts +16 -43
  36. package/src/controller/latency-controller.ts +11 -9
  37. package/src/controller/level-controller.ts +18 -12
  38. package/src/controller/stream-controller.ts +31 -36
  39. package/src/controller/subtitle-stream-controller.ts +40 -28
  40. package/src/controller/subtitle-track-controller.ts +3 -5
  41. package/src/controller/timeline-controller.ts +30 -23
  42. package/src/crypt/aes-crypto.ts +2 -21
  43. package/src/crypt/decrypter.ts +18 -32
  44. package/src/crypt/fast-aes-key.ts +5 -24
  45. package/src/demux/audio/adts.ts +4 -9
  46. package/src/demux/sample-aes.ts +0 -2
  47. package/src/demux/transmuxer-interface.ts +12 -4
  48. package/src/demux/transmuxer-worker.ts +4 -4
  49. package/src/demux/transmuxer.ts +3 -16
  50. package/src/demux/tsdemuxer.ts +37 -71
  51. package/src/demux/video/avc-video-parser.ts +119 -208
  52. package/src/demux/video/base-video-parser.ts +2 -134
  53. package/src/demux/video/exp-golomb.ts +208 -0
  54. package/src/events.ts +0 -7
  55. package/src/hls.ts +37 -49
  56. package/src/loader/fragment-loader.ts +2 -9
  57. package/src/loader/key-loader.ts +0 -2
  58. package/src/loader/level-key.ts +9 -10
  59. package/src/loader/playlist-loader.ts +5 -4
  60. package/src/remux/mp4-generator.ts +1 -196
  61. package/src/remux/mp4-remuxer.ts +7 -23
  62. package/src/task-loop.ts +2 -5
  63. package/src/types/component-api.ts +0 -2
  64. package/src/types/demuxer.ts +0 -3
  65. package/src/types/events.ts +0 -4
  66. package/src/utils/buffer-helper.ts +31 -12
  67. package/src/utils/codecs.ts +5 -34
  68. package/src/utils/logger.ts +24 -54
  69. package/src/utils/mp4-tools.ts +2 -4
  70. package/src/crypt/decrypter-aes-mode.ts +0 -4
  71. package/src/demux/video/hevc-video-parser.ts +0 -746
  72. package/src/utils/encryption-methods-util.ts +0 -21
@@ -256,7 +256,6 @@ let Events = /*#__PURE__*/function (Events) {
256
256
  Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
257
257
  Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
258
258
  Events["MEDIA_DETACHED"] = "hlsMediaDetached";
259
- Events["MEDIA_ENDED"] = "hlsMediaEnded";
260
259
  Events["BUFFER_RESET"] = "hlsBufferReset";
261
260
  Events["BUFFER_CODECS"] = "hlsBufferCodecs";
262
261
  Events["BUFFER_CREATED"] = "hlsBufferCreated";
@@ -370,6 +369,58 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
370
369
  return ErrorDetails;
371
370
  }({});
372
371
 
372
+ const noop = function noop() {};
373
+ const fakeLogger = {
374
+ trace: noop,
375
+ debug: noop,
376
+ log: noop,
377
+ warn: noop,
378
+ info: noop,
379
+ error: noop
380
+ };
381
+ let exportedLogger = fakeLogger;
382
+
383
+ // let lastCallTime;
384
+ // function formatMsgWithTimeInfo(type, msg) {
385
+ // const now = Date.now();
386
+ // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
387
+ // lastCallTime = now;
388
+ // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
389
+ // return msg;
390
+ // }
391
+
392
+ function consolePrintFn(type) {
393
+ const func = self.console[type];
394
+ if (func) {
395
+ return func.bind(self.console, `[${type}] >`);
396
+ }
397
+ return noop;
398
+ }
399
+ function exportLoggerFunctions(debugConfig, ...functions) {
400
+ functions.forEach(function (type) {
401
+ exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
402
+ });
403
+ }
404
+ function enableLogs(debugConfig, id) {
405
+ // check that console is available
406
+ if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
407
+ exportLoggerFunctions(debugConfig,
408
+ // Remove out from list here to hard-disable a log-level
409
+ // 'trace',
410
+ 'debug', 'log', 'info', 'warn', 'error');
411
+ // Some browsers don't allow to use bind on console object anyway
412
+ // fallback to default if needed
413
+ try {
414
+ exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.7"}`);
415
+ } catch (e) {
416
+ exportedLogger = fakeLogger;
417
+ }
418
+ } else {
419
+ exportedLogger = fakeLogger;
420
+ }
421
+ }
422
+ const logger = exportedLogger;
423
+
373
424
  const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
374
425
  const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
375
426
 
@@ -451,79 +502,6 @@ class AttrList {
451
502
  }
452
503
  }
453
504
 
454
- class Logger {
455
- constructor(label, logger) {
456
- this.trace = void 0;
457
- this.debug = void 0;
458
- this.log = void 0;
459
- this.warn = void 0;
460
- this.info = void 0;
461
- this.error = void 0;
462
- const lb = `[${label}]:`;
463
- this.trace = noop;
464
- this.debug = logger.debug.bind(null, lb);
465
- this.log = logger.log.bind(null, lb);
466
- this.warn = logger.warn.bind(null, lb);
467
- this.info = logger.info.bind(null, lb);
468
- this.error = logger.error.bind(null, lb);
469
- }
470
- }
471
- const noop = function noop() {};
472
- const fakeLogger = {
473
- trace: noop,
474
- debug: noop,
475
- log: noop,
476
- warn: noop,
477
- info: noop,
478
- error: noop
479
- };
480
- function createLogger() {
481
- return _extends({}, fakeLogger);
482
- }
483
-
484
- // let lastCallTime;
485
- // function formatMsgWithTimeInfo(type, msg) {
486
- // const now = Date.now();
487
- // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
488
- // lastCallTime = now;
489
- // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
490
- // return msg;
491
- // }
492
-
493
- function consolePrintFn(type, id) {
494
- const func = self.console[type];
495
- return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
496
- }
497
- function getLoggerFn(key, debugConfig, id) {
498
- return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
499
- }
500
- const exportedLogger = createLogger();
501
- function enableLogs(debugConfig, context, id) {
502
- // check that console is available
503
- const newLogger = createLogger();
504
- if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
505
- const keys = [
506
- // Remove out from list here to hard-disable a log-level
507
- // 'trace',
508
- 'debug', 'log', 'info', 'warn', 'error'];
509
- keys.forEach(key => {
510
- newLogger[key] = getLoggerFn(key, debugConfig, id);
511
- });
512
- // Some browsers don't allow to use bind on console object anyway
513
- // fallback to default if needed
514
- try {
515
- newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.7-0.canary.10040"}`);
516
- } catch (e) {
517
- /* log fn threw an exception. All logger methods are no-ops. */
518
- return createLogger();
519
- }
520
- }
521
- // global exported logger uses the log methods from last call to `enableLogs`
522
- _extends(exportedLogger, newLogger);
523
- return newLogger;
524
- }
525
- const logger = exportedLogger;
526
-
527
505
  // Avoid exporting const enum so that these values can be inlined
528
506
 
529
507
  function isDateRangeCueAttribute(attrName) {
@@ -1013,30 +991,10 @@ class LevelDetails {
1013
991
  }
1014
992
  }
1015
993
 
1016
- var DecrypterAesMode = {
1017
- cbc: 0,
1018
- ctr: 1
1019
- };
1020
-
1021
- function isFullSegmentEncryption(method) {
1022
- return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
1023
- }
1024
- function getAesModeFromFullSegmentMethod(method) {
1025
- switch (method) {
1026
- case 'AES-128':
1027
- case 'AES-256':
1028
- return DecrypterAesMode.cbc;
1029
- case 'AES-256-CTR':
1030
- return DecrypterAesMode.ctr;
1031
- default:
1032
- throw new Error(`invalid full segment method ${method}`);
1033
- }
1034
- }
1035
-
1036
994
  // This file is inserted as a shim for modules which we do not want to include into the distro.
1037
995
  // This replacement is done in the "alias" plugin of the rollup config.
1038
996
  var empty = undefined;
1039
- var HevcVideoParser = /*@__PURE__*/getDefaultExportFromCjs(empty);
997
+ var Cues = /*@__PURE__*/getDefaultExportFromCjs(empty);
1040
998
 
1041
999
  function sliceUint8(array, start, end) {
1042
1000
  // @ts-expect-error This polyfills IE11 usage of Uint8Array slice.
@@ -1668,7 +1626,7 @@ function parseStsd(stsd) {
1668
1626
  {
1669
1627
  const codecBox = findBox(sampleEntries, [fourCC])[0];
1670
1628
  const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
1671
- if (esdsBox && esdsBox.length > 7) {
1629
+ if (esdsBox && esdsBox.length > 12) {
1672
1630
  let i = 4;
1673
1631
  // ES Descriptor tag
1674
1632
  if (esdsBox[i++] !== 0x03) {
@@ -1783,9 +1741,7 @@ function parseStsd(stsd) {
1783
1741
  }
1784
1742
  function skipBERInteger(bytes, i) {
1785
1743
  const limit = i + 5;
1786
- while (bytes[i++] & 0x80 && i < limit) {
1787
- /* do nothing */
1788
- }
1744
+ while (bytes[i++] & 0x80 && i < limit) {}
1789
1745
  return i;
1790
1746
  }
1791
1747
  function toHex(x) {
@@ -2477,12 +2433,12 @@ class LevelKey {
2477
2433
  this.keyFormatVersions = formatversions;
2478
2434
  this.iv = iv;
2479
2435
  this.encrypted = method ? method !== 'NONE' : false;
2480
- this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
2436
+ this.isCommonEncryption = this.encrypted && method !== 'AES-128';
2481
2437
  }
2482
2438
  isSupported() {
2483
2439
  // If it's Segment encryption or No encryption, just select that key system
2484
2440
  if (this.method) {
2485
- if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
2441
+ if (this.method === 'AES-128' || this.method === 'NONE') {
2486
2442
  return true;
2487
2443
  }
2488
2444
  if (this.keyFormat === 'identity') {
@@ -2496,13 +2452,14 @@ class LevelKey {
2496
2452
  if (!this.encrypted || !this.uri) {
2497
2453
  return null;
2498
2454
  }
2499
- if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
2455
+ if (this.method === 'AES-128' && this.uri && !this.iv) {
2500
2456
  if (typeof sn !== 'number') {
2501
2457
  // We are fetching decryption data for a initialization segment
2502
- // If the segment was encrypted with AES-128/256
2458
+ // If the segment was encrypted with AES-128
2503
2459
  // It must have an IV defined. We cannot substitute the Segment Number in.
2504
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2505
-
2460
+ if (this.method === 'AES-128' && !this.iv) {
2461
+ logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2462
+ }
2506
2463
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
2507
2464
  sn = 0;
2508
2465
  }
@@ -2649,28 +2606,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
2649
2606
  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
2650
2607
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
2651
2608
  }
2609
+
2610
+ // Idealy fLaC and Opus would be first (spec-compliant) but
2611
+ // some browsers will report that fLaC is supported then fail.
2612
+ // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
2652
2613
  const codecsToCheck = {
2653
- // Idealy fLaC and Opus would be first (spec-compliant) but
2654
- // some browsers will report that fLaC is supported then fail.
2655
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
2656
2614
  flac: ['flac', 'fLaC', 'FLAC'],
2657
- opus: ['opus', 'Opus'],
2658
- // Replace audio codec info if browser does not support mp4a.40.34,
2659
- // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
2660
- 'mp4a.40.34': ['mp3']
2615
+ opus: ['opus', 'Opus']
2661
2616
  }[lowerCaseCodec];
2662
2617
  for (let i = 0; i < codecsToCheck.length; i++) {
2663
- var _getMediaSource;
2664
2618
  if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
2665
2619
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
2666
2620
  return codecsToCheck[i];
2667
- } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
2668
- return '';
2669
2621
  }
2670
2622
  }
2671
2623
  return lowerCaseCodec;
2672
2624
  }
2673
- const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
2625
+ const AUDIO_CODEC_REGEXP = /flac|opus/i;
2674
2626
  function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
2675
2627
  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
2676
2628
  }
@@ -2693,16 +2645,6 @@ function convertAVC1ToAVCOTI(codec) {
2693
2645
  }
2694
2646
  return codec;
2695
2647
  }
2696
- function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
2697
- const MediaSource = getMediaSource(preferManagedMediaSource) || {
2698
- isTypeSupported: () => false
2699
- };
2700
- return {
2701
- mpeg: MediaSource.isTypeSupported('audio/mpeg'),
2702
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
2703
- ac3: false
2704
- };
2705
- }
2706
2648
 
2707
2649
  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;
2708
2650
  const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -3503,10 +3445,10 @@ class PlaylistLoader {
3503
3445
  const loaderContext = loader.context;
3504
3446
  if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
3505
3447
  // same URL can't overlap
3506
- this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
3448
+ logger.trace('[playlist-loader]: playlist request ongoing');
3507
3449
  return;
3508
3450
  }
3509
- this.hls.logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3451
+ logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
3510
3452
  loader.abort();
3511
3453
  }
3512
3454
 
@@ -3616,7 +3558,7 @@ class PlaylistLoader {
3616
3558
  // alt audio rendition in which quality levels (main)
3617
3559
  // contains both audio+video. but with mixed audio track not signaled
3618
3560
  if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
3619
- this.hls.logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
3561
+ logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
3620
3562
  audioTracks.unshift({
3621
3563
  type: 'main',
3622
3564
  name: 'main',
@@ -3715,7 +3657,7 @@ class PlaylistLoader {
3715
3657
  message += ` id: ${context.id} group-id: "${context.groupId}"`;
3716
3658
  }
3717
3659
  const error = new Error(message);
3718
- this.hls.logger.warn(`[playlist-loader]: ${message}`);
3660
+ logger.warn(`[playlist-loader]: ${message}`);
3719
3661
  let details = ErrorDetails.UNKNOWN;
3720
3662
  let fatal = false;
3721
3663
  const loader = this.getInternalLoader(context);
@@ -4280,47 +4222,7 @@ class LatencyController {
4280
4222
  this.currentTime = 0;
4281
4223
  this.stallCount = 0;
4282
4224
  this._latency = null;
4283
- this.onTimeupdate = () => {
4284
- const {
4285
- media,
4286
- levelDetails
4287
- } = this;
4288
- if (!media || !levelDetails) {
4289
- return;
4290
- }
4291
- this.currentTime = media.currentTime;
4292
- const latency = this.computeLatency();
4293
- if (latency === null) {
4294
- return;
4295
- }
4296
- this._latency = latency;
4297
-
4298
- // Adapt playbackRate to meet target latency in low-latency mode
4299
- const {
4300
- lowLatencyMode,
4301
- maxLiveSyncPlaybackRate
4302
- } = this.config;
4303
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4304
- return;
4305
- }
4306
- const targetLatency = this.targetLatency;
4307
- if (targetLatency === null) {
4308
- return;
4309
- }
4310
- const distanceFromTarget = latency - targetLatency;
4311
- // Only adjust playbackRate when within one target duration of targetLatency
4312
- // and more than one second from under-buffering.
4313
- // Playback further than one target duration from target can be considered DVR playback.
4314
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4315
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4316
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4317
- const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4318
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4319
- media.playbackRate = Math.min(max, Math.max(1, rate));
4320
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4321
- media.playbackRate = 1;
4322
- }
4323
- };
4225
+ this.timeupdateHandler = () => this.timeupdate();
4324
4226
  this.hls = hls;
4325
4227
  this.config = hls.config;
4326
4228
  this.registerListeners();
@@ -4412,7 +4314,7 @@ class LatencyController {
4412
4314
  this.onMediaDetaching();
4413
4315
  this.levelDetails = null;
4414
4316
  // @ts-ignore
4415
- this.hls = null;
4317
+ this.hls = this.timeupdateHandler = null;
4416
4318
  }
4417
4319
  registerListeners() {
4418
4320
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -4430,11 +4332,11 @@ class LatencyController {
4430
4332
  }
4431
4333
  onMediaAttached(event, data) {
4432
4334
  this.media = data.media;
4433
- this.media.addEventListener('timeupdate', this.onTimeupdate);
4335
+ this.media.addEventListener('timeupdate', this.timeupdateHandler);
4434
4336
  }
4435
4337
  onMediaDetaching() {
4436
4338
  if (this.media) {
4437
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4339
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4438
4340
  this.media = null;
4439
4341
  }
4440
4342
  }
@@ -4448,10 +4350,10 @@ class LatencyController {
4448
4350
  }) {
4449
4351
  this.levelDetails = details;
4450
4352
  if (details.advanced) {
4451
- this.onTimeupdate();
4353
+ this.timeupdate();
4452
4354
  }
4453
4355
  if (!details.live && this.media) {
4454
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4356
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4455
4357
  }
4456
4358
  }
4457
4359
  onError(event, data) {
@@ -4461,7 +4363,48 @@ class LatencyController {
4461
4363
  }
4462
4364
  this.stallCount++;
4463
4365
  if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
4464
- this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
4366
+ logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
4367
+ }
4368
+ }
4369
+ timeupdate() {
4370
+ const {
4371
+ media,
4372
+ levelDetails
4373
+ } = this;
4374
+ if (!media || !levelDetails) {
4375
+ return;
4376
+ }
4377
+ this.currentTime = media.currentTime;
4378
+ const latency = this.computeLatency();
4379
+ if (latency === null) {
4380
+ return;
4381
+ }
4382
+ this._latency = latency;
4383
+
4384
+ // Adapt playbackRate to meet target latency in low-latency mode
4385
+ const {
4386
+ lowLatencyMode,
4387
+ maxLiveSyncPlaybackRate
4388
+ } = this.config;
4389
+ if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4390
+ return;
4391
+ }
4392
+ const targetLatency = this.targetLatency;
4393
+ if (targetLatency === null) {
4394
+ return;
4395
+ }
4396
+ const distanceFromTarget = latency - targetLatency;
4397
+ // Only adjust playbackRate when within one target duration of targetLatency
4398
+ // and more than one second from under-buffering.
4399
+ // Playback further than one target duration from target can be considered DVR playback.
4400
+ const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4401
+ const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4402
+ if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4403
+ const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4404
+ const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4405
+ media.playbackRate = Math.min(max, Math.max(1, rate));
4406
+ } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4407
+ media.playbackRate = 1;
4465
4408
  }
4466
4409
  }
4467
4410
  estimateLiveEdge() {
@@ -5233,13 +5176,18 @@ var ErrorActionFlags = {
5233
5176
  MoveAllAlternatesMatchingHDCP: 2,
5234
5177
  SwitchToSDR: 4
5235
5178
  }; // Reserved for future use
5236
- class ErrorController extends Logger {
5179
+ class ErrorController {
5237
5180
  constructor(hls) {
5238
- super('error-controller', hls.logger);
5239
5181
  this.hls = void 0;
5240
5182
  this.playlistError = 0;
5241
5183
  this.penalizedRenditions = {};
5184
+ this.log = void 0;
5185
+ this.warn = void 0;
5186
+ this.error = void 0;
5242
5187
  this.hls = hls;
5188
+ this.log = logger.log.bind(logger, `[info]:`);
5189
+ this.warn = logger.warn.bind(logger, `[warning]:`);
5190
+ this.error = logger.error.bind(logger, `[error]:`);
5243
5191
  this.registerListeners();
5244
5192
  }
5245
5193
  registerListeners() {
@@ -5591,13 +5539,16 @@ class ErrorController extends Logger {
5591
5539
  }
5592
5540
  }
5593
5541
 
5594
- class BasePlaylistController extends Logger {
5542
+ class BasePlaylistController {
5595
5543
  constructor(hls, logPrefix) {
5596
- super(logPrefix, hls.logger);
5597
5544
  this.hls = void 0;
5598
5545
  this.timer = -1;
5599
5546
  this.requestScheduled = -1;
5600
5547
  this.canLoad = false;
5548
+ this.log = void 0;
5549
+ this.warn = void 0;
5550
+ this.log = logger.log.bind(logger, `${logPrefix}:`);
5551
+ this.warn = logger.warn.bind(logger, `${logPrefix}:`);
5601
5552
  this.hls = hls;
5602
5553
  }
5603
5554
  destroy() {
@@ -5630,7 +5581,7 @@ class BasePlaylistController extends Logger {
5630
5581
  try {
5631
5582
  uri = new self.URL(attr.URI, previous.url).href;
5632
5583
  } catch (error) {
5633
- this.warn(`Could not construct new URL for Rendition Report: ${error}`);
5584
+ logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
5634
5585
  uri = attr.URI || '';
5635
5586
  }
5636
5587
  // Use exact match. Otherwise, the last partial match, if any, will be used
@@ -5717,12 +5668,7 @@ class BasePlaylistController extends Logger {
5717
5668
  const cdnAge = lastAdvanced + details.ageHeader;
5718
5669
  let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
5719
5670
  if (currentGoal > 0) {
5720
- if (cdnAge > details.targetduration * 3) {
5721
- // Omit segment and part directives when the last response was more than 3 target durations ago,
5722
- this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
5723
- msn = undefined;
5724
- part = undefined;
5725
- } else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
5671
+ if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
5726
5672
  // If we attempted to get the next or latest playlist update, but currentGoal increased,
5727
5673
  // then we either can't catchup, or the "age" header cannot be trusted.
5728
5674
  this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
@@ -6181,9 +6127,8 @@ function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
6181
6127
  }, {});
6182
6128
  }
6183
6129
 
6184
- class AbrController extends Logger {
6130
+ class AbrController {
6185
6131
  constructor(_hls) {
6186
- super('abr', _hls.logger);
6187
6132
  this.hls = void 0;
6188
6133
  this.lastLevelLoadSec = 0;
6189
6134
  this.lastLoadedFragLevel = -1;
@@ -6297,7 +6242,7 @@ class AbrController extends Logger {
6297
6242
  this.resetEstimator(nextLoadLevelBitrate);
6298
6243
  }
6299
6244
  this.clearTimer();
6300
- this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6245
+ logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
6301
6246
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
6302
6247
  Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
6303
6248
  Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
@@ -6317,7 +6262,7 @@ class AbrController extends Logger {
6317
6262
  }
6318
6263
  resetEstimator(abrEwmaDefaultEstimate) {
6319
6264
  if (abrEwmaDefaultEstimate) {
6320
- this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6265
+ logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6321
6266
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
6322
6267
  }
6323
6268
  this.firstSelection = -1;
@@ -6549,7 +6494,7 @@ class AbrController extends Logger {
6549
6494
  }
6550
6495
  const firstLevel = this.hls.firstLevel;
6551
6496
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
6552
- this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
6497
+ logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
6553
6498
  return clamped;
6554
6499
  }
6555
6500
  get forcedAutoLevel() {
@@ -6595,9 +6540,6 @@ class AbrController extends Logger {
6595
6540
  partCurrent,
6596
6541
  hls
6597
6542
  } = this;
6598
- if (hls.levels.length <= 1) {
6599
- return hls.loadLevel;
6600
- }
6601
6543
  const {
6602
6544
  maxAutoLevel,
6603
6545
  config,
@@ -6630,13 +6572,13 @@ class AbrController extends Logger {
6630
6572
  // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
6631
6573
  const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
6632
6574
  maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
6633
- this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
6575
+ logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
6634
6576
  // don't use conservative factor on bitrate test
6635
6577
  bwFactor = bwUpFactor = 1;
6636
6578
  }
6637
6579
  }
6638
6580
  const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
6639
- this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
6581
+ logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
6640
6582
  if (bestLevel > -1) {
6641
6583
  return bestLevel;
6642
6584
  }
@@ -6710,7 +6652,7 @@ class AbrController extends Logger {
6710
6652
  currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
6711
6653
  currentFrameRate = minFramerate;
6712
6654
  currentBw = Math.max(currentBw, minBitrate);
6713
- this.log(`picked start tier ${JSON.stringify(startTier)}`);
6655
+ logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
6714
6656
  } else {
6715
6657
  currentCodecSet = level == null ? void 0 : level.codecSet;
6716
6658
  currentVideoRange = level == null ? void 0 : level.videoRange;
@@ -6763,9 +6705,9 @@ class AbrController extends Logger {
6763
6705
  const forcedAutoLevel = this.forcedAutoLevel;
6764
6706
  if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
6765
6707
  if (levelsSkipped.length) {
6766
- this.trace(`Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
6708
+ logger.trace(`[abr] Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
6767
6709
  }
6768
- this.info(`switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
6710
+ logger.info(`[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
6769
6711
  }
6770
6712
  if (firstSelection) {
6771
6713
  this.firstSelection = i;
@@ -6808,29 +6750,40 @@ class BufferHelper {
6808
6750
  * Return true if `media`'s buffered include `position`
6809
6751
  */
6810
6752
  static isBuffered(media, position) {
6811
- if (media) {
6812
- const buffered = BufferHelper.getBuffered(media);
6813
- for (let i = buffered.length; i--;) {
6814
- if (position >= buffered.start(i) && position <= buffered.end(i)) {
6815
- return true;
6753
+ try {
6754
+ if (media) {
6755
+ const buffered = BufferHelper.getBuffered(media);
6756
+ for (let i = 0; i < buffered.length; i++) {
6757
+ if (position >= buffered.start(i) && position <= buffered.end(i)) {
6758
+ return true;
6759
+ }
6816
6760
  }
6817
6761
  }
6762
+ } catch (error) {
6763
+ // this is to catch
6764
+ // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
6765
+ // This SourceBuffer has been removed from the parent media source
6818
6766
  }
6819
6767
  return false;
6820
6768
  }
6821
6769
  static bufferInfo(media, pos, maxHoleDuration) {
6822
- if (media) {
6823
- const vbuffered = BufferHelper.getBuffered(media);
6824
- if (vbuffered.length) {
6770
+ try {
6771
+ if (media) {
6772
+ const vbuffered = BufferHelper.getBuffered(media);
6825
6773
  const buffered = [];
6826
- for (let i = 0; i < vbuffered.length; i++) {
6774
+ let i;
6775
+ for (i = 0; i < vbuffered.length; i++) {
6827
6776
  buffered.push({
6828
6777
  start: vbuffered.start(i),
6829
6778
  end: vbuffered.end(i)
6830
6779
  });
6831
6780
  }
6832
- return BufferHelper.bufferedInfo(buffered, pos, maxHoleDuration);
6781
+ return this.bufferedInfo(buffered, pos, maxHoleDuration);
6833
6782
  }
6783
+ } catch (error) {
6784
+ // this is to catch
6785
+ // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
6786
+ // This SourceBuffer has been removed from the parent media source
6834
6787
  }
6835
6788
  return {
6836
6789
  len: 0,
@@ -6842,7 +6795,14 @@ class BufferHelper {
6842
6795
  static bufferedInfo(buffered, pos, maxHoleDuration) {
6843
6796
  pos = Math.max(0, pos);
6844
6797
  // sort on buffer.start/smaller end (IE does not always return sorted buffered range)
6845
- buffered.sort((a, b) => a.start - b.start || b.end - a.end);
6798
+ buffered.sort(function (a, b) {
6799
+ const diff = a.start - b.start;
6800
+ if (diff) {
6801
+ return diff;
6802
+ } else {
6803
+ return b.end - a.end;
6804
+ }
6805
+ });
6846
6806
  let buffered2 = [];
6847
6807
  if (maxHoleDuration) {
6848
6808
  // there might be some small holes between buffer time range
@@ -6909,7 +6869,7 @@ class BufferHelper {
6909
6869
  */
6910
6870
  static getBuffered(media) {
6911
6871
  try {
6912
- return media.buffered || noopBuffered;
6872
+ return media.buffered;
6913
6873
  } catch (e) {
6914
6874
  logger.log('failed to get media.buffered', e);
6915
6875
  return noopBuffered;
@@ -6934,22 +6894,24 @@ class BufferOperationQueue {
6934
6894
  this.executeNext(type);
6935
6895
  }
6936
6896
  }
6897
+ insertAbort(operation, type) {
6898
+ const queue = this.queues[type];
6899
+ queue.unshift(operation);
6900
+ this.executeNext(type);
6901
+ }
6937
6902
  appendBlocker(type) {
6938
- return new Promise(resolve => {
6939
- const operation = {
6940
- execute: resolve,
6941
- onStart: () => {},
6942
- onComplete: () => {},
6943
- onError: () => {}
6944
- };
6945
- this.append(operation, type);
6903
+ let execute;
6904
+ const promise = new Promise(resolve => {
6905
+ execute = resolve;
6946
6906
  });
6947
- }
6948
- unblockAudio(op) {
6949
- const queue = this.queues.audio;
6950
- if (queue[0] === op) {
6951
- this.shiftAndExecuteNext('audio');
6952
- }
6907
+ const operation = {
6908
+ execute,
6909
+ onStart: () => {},
6910
+ onComplete: () => {},
6911
+ onError: () => {}
6912
+ };
6913
+ this.append(operation, type);
6914
+ return promise;
6953
6915
  }
6954
6916
  executeNext(type) {
6955
6917
  const queue = this.queues[type];
@@ -6981,9 +6943,8 @@ class BufferOperationQueue {
6981
6943
  }
6982
6944
 
6983
6945
  const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
6984
- class BufferController extends Logger {
6985
- constructor(hls, fragmentTracker) {
6986
- super('buffer-controller', hls.logger);
6946
+ class BufferController {
6947
+ constructor(hls) {
6987
6948
  // The level details used to determine duration, target-duration and live
6988
6949
  this.details = null;
6989
6950
  // cache the self generated object url to detect hijack of video tag
@@ -6993,7 +6954,6 @@ class BufferController extends Logger {
6993
6954
  // References to event listeners for each SourceBuffer, so that they can be referenced for event removal
6994
6955
  this.listeners = void 0;
6995
6956
  this.hls = void 0;
6996
- this.fragmentTracker = void 0;
6997
6957
  // The number of BUFFER_CODEC events received before any sourceBuffers are created
6998
6958
  this.bufferCodecEventsExpected = 0;
6999
6959
  // The total number of BUFFER_CODEC events received
@@ -7004,10 +6964,6 @@ class BufferController extends Logger {
7004
6964
  this.mediaSource = null;
7005
6965
  // Last MP3 audio chunk appended
7006
6966
  this.lastMpegAudioChunk = null;
7007
- // Audio fragment blocked from appending until corresponding video appends or context changes
7008
- this.blockedAudioAppend = null;
7009
- // Keep track of video append position for unblocking audio
7010
- this.lastVideoAppendEnd = 0;
7011
6967
  this.appendSource = void 0;
7012
6968
  // counters
7013
6969
  this.appendErrors = {
@@ -7018,6 +6974,9 @@ class BufferController extends Logger {
7018
6974
  this.tracks = {};
7019
6975
  this.pendingTracks = {};
7020
6976
  this.sourceBuffer = void 0;
6977
+ this.log = void 0;
6978
+ this.warn = void 0;
6979
+ this.error = void 0;
7021
6980
  this._onEndStreaming = event => {
7022
6981
  if (!this.hls) {
7023
6982
  return;
@@ -7039,10 +6998,7 @@ class BufferController extends Logger {
7039
6998
  this.log('Media source opened');
7040
6999
  if (media) {
7041
7000
  media.removeEventListener('emptied', this._onMediaEmptied);
7042
- const durationAndRange = this.getDurationAndRange();
7043
- if (durationAndRange) {
7044
- this.updateMediaSource(durationAndRange);
7045
- }
7001
+ this.updateMediaElementDuration();
7046
7002
  this.hls.trigger(Events.MEDIA_ATTACHED, {
7047
7003
  media,
7048
7004
  mediaSource: mediaSource
@@ -7066,12 +7022,15 @@ class BufferController extends Logger {
7066
7022
  _objectUrl
7067
7023
  } = this;
7068
7024
  if (mediaSrc !== _objectUrl) {
7069
- this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
7025
+ logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
7070
7026
  }
7071
7027
  };
7072
7028
  this.hls = hls;
7073
- this.fragmentTracker = fragmentTracker;
7074
- this.appendSource = hls.config.preferManagedMediaSource;
7029
+ const logPrefix = '[buffer-controller]';
7030
+ this.appendSource = hls.config.preferManagedMediaSource && typeof self !== 'undefined' && self.ManagedMediaSource;
7031
+ this.log = logger.log.bind(logger, logPrefix);
7032
+ this.warn = logger.warn.bind(logger, logPrefix);
7033
+ this.error = logger.error.bind(logger, logPrefix);
7075
7034
  this._initSourceBuffer();
7076
7035
  this.registerListeners();
7077
7036
  }
@@ -7083,13 +7042,7 @@ class BufferController extends Logger {
7083
7042
  this.details = null;
7084
7043
  this.lastMpegAudioChunk = null;
7085
7044
  // @ts-ignore
7086
- this.hls = this.fragmentTracker = null;
7087
- // @ts-ignore
7088
- this._onMediaSourceOpen = this._onMediaSourceClose = null;
7089
- // @ts-ignore
7090
- this._onMediaSourceEnded = null;
7091
- // @ts-ignore
7092
- this._onStartStreaming = this._onEndStreaming = null;
7045
+ this.hls = null;
7093
7046
  }
7094
7047
  registerListeners() {
7095
7048
  const {
@@ -7139,8 +7092,6 @@ class BufferController extends Logger {
7139
7092
  audiovideo: 0
7140
7093
  };
7141
7094
  this.lastMpegAudioChunk = null;
7142
- this.blockedAudioAppend = null;
7143
- this.lastVideoAppendEnd = 0;
7144
7095
  }
7145
7096
  onManifestLoading() {
7146
7097
  this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = 0;
@@ -7169,8 +7120,10 @@ class BufferController extends Logger {
7169
7120
  ms.addEventListener('sourceopen', this._onMediaSourceOpen);
7170
7121
  ms.addEventListener('sourceended', this._onMediaSourceEnded);
7171
7122
  ms.addEventListener('sourceclose', this._onMediaSourceClose);
7172
- ms.addEventListener('startstreaming', this._onStartStreaming);
7173
- ms.addEventListener('endstreaming', this._onEndStreaming);
7123
+ if (this.appendSource) {
7124
+ ms.addEventListener('startstreaming', this._onStartStreaming);
7125
+ ms.addEventListener('endstreaming', this._onEndStreaming);
7126
+ }
7174
7127
 
7175
7128
  // cache the locally generated object url
7176
7129
  const objectUrl = this._objectUrl = self.URL.createObjectURL(ms);
@@ -7217,8 +7170,10 @@ class BufferController extends Logger {
7217
7170
  mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen);
7218
7171
  mediaSource.removeEventListener('sourceended', this._onMediaSourceEnded);
7219
7172
  mediaSource.removeEventListener('sourceclose', this._onMediaSourceClose);
7220
- mediaSource.removeEventListener('startstreaming', this._onStartStreaming);
7221
- mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
7173
+ if (this.appendSource) {
7174
+ mediaSource.removeEventListener('startstreaming', this._onStartStreaming);
7175
+ mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
7176
+ }
7222
7177
 
7223
7178
  // Detach properly the MediaSource from the HTMLMediaElement as
7224
7179
  // suggested in https://github.com/w3c/media-source/issues/53.
@@ -7254,7 +7209,6 @@ class BufferController extends Logger {
7254
7209
  this.resetBuffer(type);
7255
7210
  });
7256
7211
  this._initSourceBuffer();
7257
- this.hls.resumeBuffering();
7258
7212
  }
7259
7213
  resetBuffer(type) {
7260
7214
  const sb = this.sourceBuffer[type];
@@ -7278,10 +7232,9 @@ class BufferController extends Logger {
7278
7232
  const trackNames = Object.keys(data);
7279
7233
  trackNames.forEach(trackName => {
7280
7234
  if (sourceBufferCount) {
7281
- var _track$buffer;
7282
7235
  // check if SourceBuffer codec needs to change
7283
7236
  const track = this.tracks[trackName];
7284
- if (track && typeof ((_track$buffer = track.buffer) == null ? void 0 : _track$buffer.changeType) === 'function') {
7237
+ if (track && typeof track.buffer.changeType === 'function') {
7285
7238
  var _trackCodec;
7286
7239
  const {
7287
7240
  id,
@@ -7296,7 +7249,7 @@ class BufferController extends Logger {
7296
7249
  const nextCodec = (_trackCodec = trackCodec) == null ? void 0 : _trackCodec.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');
7297
7250
  if (trackCodec && currentCodec !== nextCodec) {
7298
7251
  if (trackName.slice(0, 5) === 'audio') {
7299
- trackCodec = getCodecCompatibleName(trackCodec, this.hls.config.preferManagedMediaSource);
7252
+ trackCodec = getCodecCompatibleName(trackCodec, this.appendSource);
7300
7253
  }
7301
7254
  const mimeType = `${container};codecs=${trackCodec}`;
7302
7255
  this.appendChangeType(trackName, mimeType);
@@ -7351,54 +7304,20 @@ class BufferController extends Logger {
7351
7304
  };
7352
7305
  operationQueue.append(operation, type, !!this.pendingTracks[type]);
7353
7306
  }
7354
- blockAudio(partOrFrag) {
7355
- var _this$fragmentTracker;
7356
- const pStart = partOrFrag.start;
7357
- const pTime = pStart + partOrFrag.duration * 0.05;
7358
- const atGap = ((_this$fragmentTracker = this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker.gap) === true;
7359
- if (atGap) {
7360
- return;
7361
- }
7362
- const op = {
7363
- execute: () => {
7364
- var _this$fragmentTracker2;
7365
- if (this.lastVideoAppendEnd > pTime || this.sourceBuffer.video && BufferHelper.isBuffered(this.sourceBuffer.video, pTime) || ((_this$fragmentTracker2 = this.fragmentTracker.getAppendedFrag(pTime, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker2.gap) === true) {
7366
- this.blockedAudioAppend = null;
7367
- this.operationQueue.shiftAndExecuteNext('audio');
7368
- }
7369
- },
7370
- onStart: () => {},
7371
- onComplete: () => {},
7372
- onError: () => {}
7373
- };
7374
- this.blockedAudioAppend = {
7375
- op,
7376
- frag: partOrFrag
7377
- };
7378
- this.operationQueue.append(op, 'audio', true);
7379
- }
7380
- unblockAudio() {
7381
- const blockedAudioAppend = this.blockedAudioAppend;
7382
- if (blockedAudioAppend) {
7383
- this.blockedAudioAppend = null;
7384
- this.operationQueue.unblockAudio(blockedAudioAppend.op);
7385
- }
7386
- }
7387
7307
  onBufferAppending(event, eventData) {
7388
7308
  const {
7309
+ hls,
7389
7310
  operationQueue,
7390
7311
  tracks
7391
7312
  } = this;
7392
7313
  const {
7393
7314
  data,
7394
7315
  type,
7395
- parent,
7396
7316
  frag,
7397
7317
  part,
7398
7318
  chunkMeta
7399
7319
  } = eventData;
7400
7320
  const chunkStats = chunkMeta.buffering[type];
7401
- const sn = frag.sn;
7402
7321
  const bufferAppendingStart = self.performance.now();
7403
7322
  chunkStats.start = bufferAppendingStart;
7404
7323
  const fragBuffering = frag.stats.buffering;
@@ -7421,36 +7340,7 @@ class BufferController extends Logger {
7421
7340
  checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn;
7422
7341
  this.lastMpegAudioChunk = chunkMeta;
7423
7342
  }
7424
-
7425
- // Block audio append until overlapping video append
7426
- const videoSb = this.sourceBuffer.video;
7427
- if (videoSb && sn !== 'initSegment') {
7428
- const partOrFrag = part || frag;
7429
- const blockedAudioAppend = this.blockedAudioAppend;
7430
- if (type === 'audio' && parent !== 'main' && !this.blockedAudioAppend) {
7431
- const pStart = partOrFrag.start;
7432
- const pTime = pStart + partOrFrag.duration * 0.05;
7433
- const vbuffered = videoSb.buffered;
7434
- const vappending = this.operationQueue.current('video');
7435
- if (!vbuffered.length && !vappending) {
7436
- // wait for video before appending audio
7437
- this.blockAudio(partOrFrag);
7438
- } else if (!vappending && !BufferHelper.isBuffered(videoSb, pTime) && this.lastVideoAppendEnd < pTime) {
7439
- // audio is ahead of video
7440
- this.blockAudio(partOrFrag);
7441
- }
7442
- } else if (type === 'video') {
7443
- const videoAppendEnd = partOrFrag.end;
7444
- if (blockedAudioAppend) {
7445
- const audioStart = blockedAudioAppend.frag.start;
7446
- if (videoAppendEnd > audioStart || videoAppendEnd < this.lastVideoAppendEnd || BufferHelper.isBuffered(videoSb, audioStart)) {
7447
- this.unblockAudio();
7448
- }
7449
- }
7450
- this.lastVideoAppendEnd = videoAppendEnd;
7451
- }
7452
- }
7453
- const fragStart = (part || frag).start;
7343
+ const fragStart = frag.start;
7454
7344
  const operation = {
7455
7345
  execute: () => {
7456
7346
  chunkStats.executeStart = self.performance.now();
@@ -7459,7 +7349,7 @@ class BufferController extends Logger {
7459
7349
  if (sb) {
7460
7350
  const delta = fragStart - sb.timestampOffset;
7461
7351
  if (Math.abs(delta) >= 0.1) {
7462
- this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`);
7352
+ this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${frag.sn})`);
7463
7353
  sb.timestampOffset = fragStart;
7464
7354
  }
7465
7355
  }
@@ -7526,21 +7416,22 @@ class BufferController extends Logger {
7526
7416
  /* with UHD content, we could get loop of quota exceeded error until
7527
7417
  browser is able to evict some data from sourcebuffer. Retrying can help recover.
7528
7418
  */
7529
- this.warn(`Failed ${appendErrorCount}/${this.hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
7530
- if (appendErrorCount >= this.hls.config.appendErrorMaxRetry) {
7419
+ this.warn(`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
7420
+ if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
7531
7421
  event.fatal = true;
7532
7422
  }
7533
7423
  }
7534
- this.hls.trigger(Events.ERROR, event);
7424
+ hls.trigger(Events.ERROR, event);
7535
7425
  }
7536
7426
  };
7537
7427
  operationQueue.append(operation, type, !!this.pendingTracks[type]);
7538
7428
  }
7539
- getFlushOp(type, start, end) {
7540
- return {
7541
- execute: () => {
7542
- this.removeExecutor(type, start, end);
7543
- },
7429
+ onBufferFlushing(event, data) {
7430
+ const {
7431
+ operationQueue
7432
+ } = this;
7433
+ const flushOperation = type => ({
7434
+ execute: this.removeExecutor.bind(this, type, data.startOffset, data.endOffset),
7544
7435
  onStart: () => {
7545
7436
  // logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
7546
7437
  },
@@ -7553,22 +7444,12 @@ class BufferController extends Logger {
7553
7444
  onError: error => {
7554
7445
  this.warn(`Failed to remove from ${type} SourceBuffer`, error);
7555
7446
  }
7556
- };
7557
- }
7558
- onBufferFlushing(event, data) {
7559
- const {
7560
- operationQueue
7561
- } = this;
7562
- const {
7563
- type,
7564
- startOffset,
7565
- endOffset
7566
- } = data;
7567
- if (type) {
7568
- operationQueue.append(this.getFlushOp(type, startOffset, endOffset), type);
7447
+ });
7448
+ if (data.type) {
7449
+ operationQueue.append(flushOperation(data.type), data.type);
7569
7450
  } else {
7570
- this.getSourceBufferTypes().forEach(sbType => {
7571
- operationQueue.append(this.getFlushOp(sbType, startOffset, endOffset), sbType);
7451
+ this.getSourceBufferTypes().forEach(type => {
7452
+ operationQueue.append(flushOperation(type), type);
7572
7453
  });
7573
7454
  }
7574
7455
  }
@@ -7615,9 +7496,6 @@ class BufferController extends Logger {
7615
7496
  // on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
7616
7497
  // an undefined data.type will mark all buffers as EOS.
7617
7498
  onBufferEos(event, data) {
7618
- if (data.type === 'video') {
7619
- this.unblockAudio();
7620
- }
7621
7499
  const ended = this.getSourceBufferTypes().reduce((acc, type) => {
7622
7500
  const sb = this.sourceBuffer[type];
7623
7501
  if (sb && (!data.type || data.type === type)) {
@@ -7660,14 +7538,10 @@ class BufferController extends Logger {
7660
7538
  return;
7661
7539
  }
7662
7540
  this.details = details;
7663
- const durationAndRange = this.getDurationAndRange();
7664
- if (!durationAndRange) {
7665
- return;
7666
- }
7667
7541
  if (this.getSourceBufferTypes().length) {
7668
- this.blockBuffers(() => this.updateMediaSource(durationAndRange));
7542
+ this.blockBuffers(this.updateMediaElementDuration.bind(this));
7669
7543
  } else {
7670
- this.updateMediaSource(durationAndRange);
7544
+ this.updateMediaElementDuration();
7671
7545
  }
7672
7546
  }
7673
7547
  trimBuffers() {
@@ -7772,9 +7646,9 @@ class BufferController extends Logger {
7772
7646
  * 'liveDurationInfinity` is set to `true`
7773
7647
  * More details: https://github.com/video-dev/hls.js/issues/355
7774
7648
  */
7775
- getDurationAndRange() {
7649
+ updateMediaElementDuration() {
7776
7650
  if (!this.details || !this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
7777
- return null;
7651
+ return;
7778
7652
  }
7779
7653
  const {
7780
7654
  details,
@@ -7788,41 +7662,25 @@ class BufferController extends Logger {
7788
7662
  if (details.live && hls.config.liveDurationInfinity) {
7789
7663
  // Override duration to Infinity
7790
7664
  mediaSource.duration = Infinity;
7791
- const len = details.fragments.length;
7792
- if (len && details.live && !!mediaSource.setLiveSeekableRange) {
7793
- const start = Math.max(0, details.fragments[0].start);
7794
- const end = Math.max(start, start + details.totalduration);
7795
- return {
7796
- duration: Infinity,
7797
- start,
7798
- end
7799
- };
7800
- }
7801
- return {
7802
- duration: Infinity
7803
- };
7665
+ this.updateSeekableRange(details);
7804
7666
  } else if (levelDuration > msDuration && levelDuration > mediaDuration || !isFiniteNumber(mediaDuration)) {
7805
- return {
7806
- duration: levelDuration
7807
- };
7667
+ // levelDuration was the last value we set.
7668
+ // not using mediaSource.duration as the browser may tweak this value
7669
+ // only update Media Source duration if its value increase, this is to avoid
7670
+ // flushing already buffered portion when switching between quality level
7671
+ this.log(`Updating Media Source duration to ${levelDuration.toFixed(3)}`);
7672
+ mediaSource.duration = levelDuration;
7808
7673
  }
7809
- return null;
7810
7674
  }
7811
- updateMediaSource({
7812
- duration,
7813
- start,
7814
- end
7815
- }) {
7816
- if (!this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
7817
- return;
7818
- }
7819
- if (isFiniteNumber(duration)) {
7820
- this.log(`Updating Media Source duration to ${duration.toFixed(3)}`);
7821
- }
7822
- this.mediaSource.duration = duration;
7823
- if (start !== undefined && end !== undefined) {
7824
- this.log(`Media Source duration is set to ${this.mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
7825
- this.mediaSource.setLiveSeekableRange(start, end);
7675
+ updateSeekableRange(levelDetails) {
7676
+ const mediaSource = this.mediaSource;
7677
+ const fragments = levelDetails.fragments;
7678
+ const len = fragments.length;
7679
+ if (len && levelDetails.live && mediaSource != null && mediaSource.setLiveSeekableRange) {
7680
+ const start = Math.max(0, fragments[0].start);
7681
+ const end = Math.max(start, start + levelDetails.totalduration);
7682
+ this.log(`Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
7683
+ mediaSource.setLiveSeekableRange(start, end);
7826
7684
  }
7827
7685
  }
7828
7686
  checkPendingTracks() {
@@ -7880,7 +7738,7 @@ class BufferController extends Logger {
7880
7738
  let codec = track.levelCodec || track.codec;
7881
7739
  if (codec) {
7882
7740
  if (trackName.slice(0, 5) === 'audio') {
7883
- codec = getCodecCompatibleName(codec, this.hls.config.preferManagedMediaSource);
7741
+ codec = getCodecCompatibleName(codec, this.appendSource);
7884
7742
  }
7885
7743
  }
7886
7744
  const mimeType = `${track.container};codecs=${codec}`;
@@ -7892,15 +7750,17 @@ class BufferController extends Logger {
7892
7750
  this.addBufferListener(sbName, 'updateend', this._onSBUpdateEnd);
7893
7751
  this.addBufferListener(sbName, 'error', this._onSBUpdateError);
7894
7752
  // ManagedSourceBuffer bufferedchange event
7895
- this.addBufferListener(sbName, 'bufferedchange', (type, event) => {
7896
- // If media was ejected check for a change. Added ranges are redundant with changes on 'updateend' event.
7897
- const removedRanges = event.removedRanges;
7898
- if (removedRanges != null && removedRanges.length) {
7899
- this.hls.trigger(Events.BUFFER_FLUSHED, {
7900
- type: trackName
7901
- });
7902
- }
7903
- });
7753
+ if (this.appendSource) {
7754
+ this.addBufferListener(sbName, 'bufferedchange', (type, event) => {
7755
+ // If media was ejected check for a change. Added ranges are redundant with changes on 'updateend' event.
7756
+ const removedRanges = event.removedRanges;
7757
+ if (removedRanges != null && removedRanges.length) {
7758
+ this.hls.trigger(Events.BUFFER_FLUSHED, {
7759
+ type: trackName
7760
+ });
7761
+ }
7762
+ });
7763
+ }
7904
7764
  this.tracks[trackName] = {
7905
7765
  buffer: sb,
7906
7766
  codec: codec,
@@ -8005,7 +7865,6 @@ class BufferController extends Logger {
8005
7865
  }
8006
7866
  return;
8007
7867
  }
8008
- sb.ending = false;
8009
7868
  sb.ended = false;
8010
7869
  sb.appendBuffer(data);
8011
7870
  }
@@ -8025,14 +7884,10 @@ class BufferController extends Logger {
8025
7884
 
8026
7885
  // logger.debug(`[buffer-controller]: Blocking ${buffers} SourceBuffer`);
8027
7886
  const blockingOperations = buffers.map(type => operationQueue.appendBlocker(type));
8028
- const audioBlocked = buffers.length > 1 && !!this.blockedAudioAppend;
8029
- if (audioBlocked) {
8030
- this.unblockAudio();
8031
- }
8032
- Promise.all(blockingOperations).then(result => {
7887
+ Promise.all(blockingOperations).then(() => {
8033
7888
  // logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
8034
7889
  onUnblocked();
8035
- buffers.forEach((type, i) => {
7890
+ buffers.forEach(type => {
8036
7891
  const sb = this.sourceBuffer[type];
8037
7892
  // Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
8038
7893
  // true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration)
@@ -8193,10 +8048,10 @@ class CapLevelController {
8193
8048
  const hls = this.hls;
8194
8049
  const maxLevel = this.getMaxLevel(levels.length - 1);
8195
8050
  if (maxLevel !== this.autoLevelCapping) {
8196
- hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
8051
+ logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
8197
8052
  }
8198
8053
  hls.autoLevelCapping = maxLevel;
8199
- if (hls.autoLevelEnabled && hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
8054
+ if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
8200
8055
  // if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch
8201
8056
  // usually happen when the user go to the fullscreen mode.
8202
8057
  this.streamController.nextLevelSwitch();
@@ -8371,10 +8226,10 @@ class FPSController {
8371
8226
  totalDroppedFrames: droppedFrames
8372
8227
  });
8373
8228
  if (droppedFPS > 0) {
8374
- // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
8229
+ // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
8375
8230
  if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
8376
8231
  let currentLevel = hls.currentLevel;
8377
- hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
8232
+ logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
8378
8233
  if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
8379
8234
  currentLevel = currentLevel - 1;
8380
8235
  hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
@@ -8407,10 +8262,10 @@ class FPSController {
8407
8262
  }
8408
8263
 
8409
8264
  const PATHWAY_PENALTY_DURATION_MS = 300000;
8410
- class ContentSteeringController extends Logger {
8265
+ class ContentSteeringController {
8411
8266
  constructor(hls) {
8412
- super('content-steering', hls.logger);
8413
8267
  this.hls = void 0;
8268
+ this.log = void 0;
8414
8269
  this.loader = null;
8415
8270
  this.uri = null;
8416
8271
  this.pathwayId = '.';
@@ -8425,6 +8280,7 @@ class ContentSteeringController extends Logger {
8425
8280
  this.subtitleTracks = null;
8426
8281
  this.penalizedPathways = {};
8427
8282
  this.hls = hls;
8283
+ this.log = logger.log.bind(logger, `[content-steering]:`);
8428
8284
  this.registerListeners();
8429
8285
  }
8430
8286
  registerListeners() {
@@ -8548,7 +8404,7 @@ class ContentSteeringController extends Logger {
8548
8404
  errorAction.resolved = this.pathwayId !== errorPathway;
8549
8405
  }
8550
8406
  if (!errorAction.resolved) {
8551
- this.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
8407
+ logger.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
8552
8408
  }
8553
8409
  }
8554
8410
  }
@@ -8719,7 +8575,7 @@ class ContentSteeringController extends Logger {
8719
8575
  onSuccess: (response, stats, context, networkDetails) => {
8720
8576
  this.log(`Loaded steering manifest: "${url}"`);
8721
8577
  const steeringData = response.data;
8722
- if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
8578
+ if (steeringData.VERSION !== 1) {
8723
8579
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
8724
8580
  return;
8725
8581
  }
@@ -9627,7 +9483,7 @@ const hlsDefaultConfig = _objectSpread2(_objectSpread2({
9627
9483
  });
9628
9484
  function timelineConfig() {
9629
9485
  return {
9630
- cueHandler: HevcVideoParser,
9486
+ cueHandler: Cues,
9631
9487
  // used by timeline-controller
9632
9488
  enableWebVTT: false,
9633
9489
  // used by timeline-controller
@@ -9658,7 +9514,7 @@ function timelineConfig() {
9658
9514
  /**
9659
9515
  * @ignore
9660
9516
  */
9661
- function mergeConfig(defaultConfig, userConfig, logger) {
9517
+ function mergeConfig(defaultConfig, userConfig) {
9662
9518
  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
9663
9519
  throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
9664
9520
  }
@@ -9728,7 +9584,7 @@ function deepCpy(obj) {
9728
9584
  /**
9729
9585
  * @ignore
9730
9586
  */
9731
- function enableStreamingMode(config, logger) {
9587
+ function enableStreamingMode(config) {
9732
9588
  const currentLoader = config.loader;
9733
9589
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
9734
9590
  // If a developer has configured their own loader, respect that choice
@@ -9745,9 +9601,10 @@ function enableStreamingMode(config, logger) {
9745
9601
  }
9746
9602
  }
9747
9603
 
9604
+ let chromeOrFirefox;
9748
9605
  class LevelController extends BasePlaylistController {
9749
9606
  constructor(hls, contentSteeringController) {
9750
- super(hls, 'level-controller');
9607
+ super(hls, '[level-controller]');
9751
9608
  this._levels = [];
9752
9609
  this._firstLevel = -1;
9753
9610
  this._maxAutoLevel = -1;
@@ -9818,15 +9675,23 @@ class LevelController extends BasePlaylistController {
9818
9675
  let videoCodecFound = false;
9819
9676
  let audioCodecFound = false;
9820
9677
  data.levels.forEach(levelParsed => {
9821
- var _videoCodec;
9678
+ var _audioCodec, _videoCodec;
9822
9679
  const attributes = levelParsed.attrs;
9680
+
9681
+ // erase audio codec info if browser does not support mp4a.40.34.
9682
+ // demuxer will autodetect codec and fallback to mpeg/audio
9823
9683
  let {
9824
9684
  audioCodec,
9825
9685
  videoCodec
9826
9686
  } = levelParsed;
9687
+ if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
9688
+ chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
9689
+ if (chromeOrFirefox) {
9690
+ levelParsed.audioCodec = audioCodec = undefined;
9691
+ }
9692
+ }
9827
9693
  if (audioCodec) {
9828
- // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
9829
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
9694
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
9830
9695
  }
9831
9696
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
9832
9697
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -10168,12 +10033,7 @@ class LevelController extends BasePlaylistController {
10168
10033
  if (curLevel.fragmentError === 0) {
10169
10034
  curLevel.loadError = 0;
10170
10035
  }
10171
- // Ignore matching details populated by loading a Media Playlist directly
10172
- let previousDetails = curLevel.details;
10173
- if (previousDetails === data.details && previousDetails.advanced) {
10174
- previousDetails = undefined;
10175
- }
10176
- this.playlistLoaded(level, data, previousDetails);
10036
+ this.playlistLoaded(level, data, curLevel.details);
10177
10037
  } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
10178
10038
  // received a delta playlist update that cannot be merged
10179
10039
  details.deltaUpdateFailed = true;
@@ -10351,16 +10211,13 @@ class FragmentTracker {
10351
10211
  * If not found any Fragment, return null
10352
10212
  */
10353
10213
  getBufferedFrag(position, levelType) {
10354
- return this.getFragAtPos(position, levelType, true);
10355
- }
10356
- getFragAtPos(position, levelType, buffered) {
10357
10214
  const {
10358
10215
  fragments
10359
10216
  } = this;
10360
10217
  const keys = Object.keys(fragments);
10361
10218
  for (let i = keys.length; i--;) {
10362
10219
  const fragmentEntity = fragments[keys[i]];
10363
- if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && (!buffered || fragmentEntity.buffered)) {
10220
+ if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && fragmentEntity.buffered) {
10364
10221
  const frag = fragmentEntity.body;
10365
10222
  if (frag.start <= position && position <= frag.end) {
10366
10223
  return frag;
@@ -10615,8 +10472,7 @@ class FragmentTracker {
10615
10472
  const {
10616
10473
  frag,
10617
10474
  part,
10618
- timeRanges,
10619
- type
10475
+ timeRanges
10620
10476
  } = data;
10621
10477
  if (frag.sn === 'initSegment') {
10622
10478
  return;
@@ -10631,8 +10487,10 @@ class FragmentTracker {
10631
10487
  }
10632
10488
  // Store the latest timeRanges loaded in the buffer
10633
10489
  this.timeRanges = timeRanges;
10634
- const timeRange = timeRanges[type];
10635
- this.detectEvictedFragments(type, timeRange, playlistType, part);
10490
+ Object.keys(timeRanges).forEach(elementaryStream => {
10491
+ const timeRange = timeRanges[elementaryStream];
10492
+ this.detectEvictedFragments(elementaryStream, timeRange, playlistType, part);
10493
+ });
10636
10494
  }
10637
10495
  onFragBuffered(event, data) {
10638
10496
  this.detectPartialFragments(data);
@@ -10961,8 +10819,8 @@ function createLoaderContext(frag, part = null) {
10961
10819
  var _frag$decryptdata;
10962
10820
  let byteRangeStart = start;
10963
10821
  let byteRangeEnd = end;
10964
- if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
10965
- // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
10822
+ if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
10823
+ // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
10966
10824
  // has the unencrypted size specified in the range.
10967
10825
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
10968
10826
  const fragmentLen = end - start;
@@ -10995,9 +10853,6 @@ function createGapLoadError(frag, part) {
10995
10853
  (part ? part : frag).stats.aborted = true;
10996
10854
  return new LoadError(errorData);
10997
10855
  }
10998
- function isMethodFullSegmentAesCbc(method) {
10999
- return method === 'AES-128' || method === 'AES-256';
11000
- }
11001
10856
  class LoadError extends Error {
11002
10857
  constructor(data) {
11003
10858
  super(data.error.message);
@@ -11143,8 +10998,6 @@ class KeyLoader {
11143
10998
  }
11144
10999
  return this.loadKeyEME(keyInfo, frag);
11145
11000
  case 'AES-128':
11146
- case 'AES-256':
11147
- case 'AES-256-CTR':
11148
11001
  return this.loadKeyHTTP(keyInfo, frag);
11149
11002
  default:
11150
11003
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -11280,9 +11133,8 @@ class KeyLoader {
11280
11133
  * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
11281
11134
  * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
11282
11135
  */
11283
- class TaskLoop extends Logger {
11284
- constructor(label, logger) {
11285
- super(label, logger);
11136
+ class TaskLoop {
11137
+ constructor() {
11286
11138
  this._boundTick = void 0;
11287
11139
  this._tickTimer = null;
11288
11140
  this._tickInterval = null;
@@ -11550,61 +11402,33 @@ function alignMediaPlaylistByPDT(details, refDetails) {
11550
11402
  }
11551
11403
 
11552
11404
  class AESCrypto {
11553
- constructor(subtle, iv, aesMode) {
11405
+ constructor(subtle, iv) {
11554
11406
  this.subtle = void 0;
11555
11407
  this.aesIV = void 0;
11556
- this.aesMode = void 0;
11557
11408
  this.subtle = subtle;
11558
11409
  this.aesIV = iv;
11559
- this.aesMode = aesMode;
11560
11410
  }
11561
11411
  decrypt(data, key) {
11562
- switch (this.aesMode) {
11563
- case DecrypterAesMode.cbc:
11564
- return this.subtle.decrypt({
11565
- name: 'AES-CBC',
11566
- iv: this.aesIV
11567
- }, key, data);
11568
- case DecrypterAesMode.ctr:
11569
- return this.subtle.decrypt({
11570
- name: 'AES-CTR',
11571
- counter: this.aesIV,
11572
- length: 64
11573
- },
11574
- //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
11575
- key, data);
11576
- default:
11577
- throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
11578
- }
11412
+ return this.subtle.decrypt({
11413
+ name: 'AES-CBC',
11414
+ iv: this.aesIV
11415
+ }, key, data);
11579
11416
  }
11580
11417
  }
11581
11418
 
11582
11419
  class FastAESKey {
11583
- constructor(subtle, key, aesMode) {
11420
+ constructor(subtle, key) {
11584
11421
  this.subtle = void 0;
11585
11422
  this.key = void 0;
11586
- this.aesMode = void 0;
11587
11423
  this.subtle = subtle;
11588
11424
  this.key = key;
11589
- this.aesMode = aesMode;
11590
11425
  }
11591
11426
  expandKey() {
11592
- const subtleAlgoName = getSubtleAlgoName(this.aesMode);
11593
11427
  return this.subtle.importKey('raw', this.key, {
11594
- name: subtleAlgoName
11428
+ name: 'AES-CBC'
11595
11429
  }, false, ['encrypt', 'decrypt']);
11596
11430
  }
11597
11431
  }
11598
- function getSubtleAlgoName(aesMode) {
11599
- switch (aesMode) {
11600
- case DecrypterAesMode.cbc:
11601
- return 'AES-CBC';
11602
- case DecrypterAesMode.ctr:
11603
- return 'AES-CTR';
11604
- default:
11605
- throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
11606
- }
11607
- }
11608
11432
 
11609
11433
  // PKCS7
11610
11434
  function removePadding(array) {
@@ -11854,8 +11678,7 @@ class Decrypter {
11854
11678
  this.currentIV = null;
11855
11679
  this.currentResult = null;
11856
11680
  this.useSoftware = void 0;
11857
- this.enableSoftwareAES = void 0;
11858
- this.enableSoftwareAES = config.enableSoftwareAES;
11681
+ this.useSoftware = config.enableSoftwareAES;
11859
11682
  this.removePKCS7Padding = removePKCS7Padding;
11860
11683
  // built in decryptor expects PKCS7 padding
11861
11684
  if (removePKCS7Padding) {
@@ -11868,7 +11691,9 @@ class Decrypter {
11868
11691
  /* no-op */
11869
11692
  }
11870
11693
  }
11871
- this.useSoftware = this.subtle === null;
11694
+ if (this.subtle === null) {
11695
+ this.useSoftware = true;
11696
+ }
11872
11697
  }
11873
11698
  destroy() {
11874
11699
  this.subtle = null;
@@ -11906,10 +11731,10 @@ class Decrypter {
11906
11731
  this.softwareDecrypter = null;
11907
11732
  }
11908
11733
  }
11909
- decrypt(data, key, iv, aesMode) {
11734
+ decrypt(data, key, iv) {
11910
11735
  if (this.useSoftware) {
11911
11736
  return new Promise((resolve, reject) => {
11912
- this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
11737
+ this.softwareDecrypt(new Uint8Array(data), key, iv);
11913
11738
  const decryptResult = this.flush();
11914
11739
  if (decryptResult) {
11915
11740
  resolve(decryptResult.buffer);
@@ -11918,21 +11743,17 @@ class Decrypter {
11918
11743
  }
11919
11744
  });
11920
11745
  }
11921
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
11746
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
11922
11747
  }
11923
11748
 
11924
11749
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
11925
11750
  // data is handled in the flush() call
11926
- softwareDecrypt(data, key, iv, aesMode) {
11751
+ softwareDecrypt(data, key, iv) {
11927
11752
  const {
11928
11753
  currentIV,
11929
11754
  currentResult,
11930
11755
  remainderData
11931
11756
  } = this;
11932
- if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
11933
- logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
11934
- return null;
11935
- }
11936
11757
  this.logOnce('JS AES decrypt');
11937
11758
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
11938
11759
  // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
@@ -11965,11 +11786,11 @@ class Decrypter {
11965
11786
  }
11966
11787
  return result;
11967
11788
  }
11968
- webCryptoDecrypt(data, key, iv, aesMode) {
11789
+ webCryptoDecrypt(data, key, iv) {
11969
11790
  const subtle = this.subtle;
11970
11791
  if (this.key !== key || !this.fastAesKey) {
11971
11792
  this.key = key;
11972
- this.fastAesKey = new FastAESKey(subtle, key, aesMode);
11793
+ this.fastAesKey = new FastAESKey(subtle, key);
11973
11794
  }
11974
11795
  return this.fastAesKey.expandKey().then(aesKey => {
11975
11796
  // decrypt using web crypto
@@ -11977,25 +11798,22 @@ class Decrypter {
11977
11798
  return Promise.reject(new Error('web crypto not initialized'));
11978
11799
  }
11979
11800
  this.logOnce('WebCrypto AES decrypt');
11980
- const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
11801
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv));
11981
11802
  return crypto.decrypt(data.buffer, aesKey);
11982
11803
  }).catch(err => {
11983
11804
  logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
11984
- return this.onWebCryptoError(data, key, iv, aesMode);
11805
+ return this.onWebCryptoError(data, key, iv);
11985
11806
  });
11986
11807
  }
11987
- onWebCryptoError(data, key, iv, aesMode) {
11988
- const enableSoftwareAES = this.enableSoftwareAES;
11989
- if (enableSoftwareAES) {
11990
- this.useSoftware = true;
11991
- this.logEnabled = true;
11992
- this.softwareDecrypt(data, key, iv, aesMode);
11993
- const decryptResult = this.flush();
11994
- if (decryptResult) {
11995
- return decryptResult.buffer;
11996
- }
11808
+ onWebCryptoError(data, key, iv) {
11809
+ this.useSoftware = true;
11810
+ this.logEnabled = true;
11811
+ this.softwareDecrypt(data, key, iv);
11812
+ const decryptResult = this.flush();
11813
+ if (decryptResult) {
11814
+ return decryptResult.buffer;
11997
11815
  }
11998
- throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
11816
+ throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
11999
11817
  }
12000
11818
  getValidChunk(data) {
12001
11819
  let currentChunk = data;
@@ -12046,7 +11864,7 @@ const State = {
12046
11864
  };
12047
11865
  class BaseStreamController extends TaskLoop {
12048
11866
  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
12049
- super(logPrefix, hls.logger);
11867
+ super();
12050
11868
  this.hls = void 0;
12051
11869
  this.fragPrevious = null;
12052
11870
  this.fragCurrent = null;
@@ -12071,98 +11889,22 @@ class BaseStreamController extends TaskLoop {
12071
11889
  this.startFragRequested = false;
12072
11890
  this.decrypter = void 0;
12073
11891
  this.initPTS = [];
12074
- this.buffering = true;
12075
- this.loadingParts = false;
12076
- this.onMediaSeeking = () => {
12077
- const {
12078
- config,
12079
- fragCurrent,
12080
- media,
12081
- mediaBuffer,
12082
- state
12083
- } = this;
12084
- const currentTime = media ? media.currentTime : 0;
12085
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
12086
- this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
12087
- if (this.state === State.ENDED) {
12088
- this.resetLoadingState();
12089
- } else if (fragCurrent) {
12090
- // Seeking while frag load is in progress
12091
- const tolerance = config.maxFragLookUpTolerance;
12092
- const fragStartOffset = fragCurrent.start - tolerance;
12093
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
12094
- // if seeking out of buffered range or into new one
12095
- if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
12096
- const pastFragment = currentTime > fragEndOffset;
12097
- // if the seek position is outside the current fragment range
12098
- if (currentTime < fragStartOffset || pastFragment) {
12099
- if (pastFragment && fragCurrent.loader) {
12100
- this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
12101
- fragCurrent.abortRequests();
12102
- this.resetLoadingState();
12103
- }
12104
- this.fragPrevious = null;
12105
- }
12106
- }
12107
- }
12108
- if (media) {
12109
- // Remove gap fragments
12110
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
12111
- this.lastCurrentTime = currentTime;
12112
- if (!this.loadingParts) {
12113
- const bufferEnd = Math.max(bufferInfo.end, currentTime);
12114
- const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
12115
- if (shouldLoadParts) {
12116
- this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
12117
- this.loadingParts = shouldLoadParts;
12118
- }
12119
- }
12120
- }
12121
-
12122
- // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
12123
- if (!this.loadedmetadata && !bufferInfo.len) {
12124
- this.nextLoadPosition = this.startPosition = currentTime;
12125
- }
12126
-
12127
- // Async tick to speed up processing
12128
- this.tickImmediate();
12129
- };
12130
- this.onMediaEnded = () => {
12131
- // reset startPosition and lastCurrentTime to restart playback @ stream beginning
12132
- this.startPosition = this.lastCurrentTime = 0;
12133
- if (this.playlistType === PlaylistLevelType.MAIN) {
12134
- this.hls.trigger(Events.MEDIA_ENDED, {
12135
- stalled: false
12136
- });
12137
- }
12138
- };
11892
+ this.onvseeking = null;
11893
+ this.onvended = null;
11894
+ this.logPrefix = '';
11895
+ this.log = void 0;
11896
+ this.warn = void 0;
12139
11897
  this.playlistType = playlistType;
11898
+ this.logPrefix = logPrefix;
11899
+ this.log = logger.log.bind(logger, `${logPrefix}:`);
11900
+ this.warn = logger.warn.bind(logger, `${logPrefix}:`);
12140
11901
  this.hls = hls;
12141
11902
  this.fragmentLoader = new FragmentLoader(hls.config);
12142
11903
  this.keyLoader = keyLoader;
12143
11904
  this.fragmentTracker = fragmentTracker;
12144
11905
  this.config = hls.config;
12145
11906
  this.decrypter = new Decrypter(hls.config);
12146
- }
12147
- registerListeners() {
12148
- const {
12149
- hls
12150
- } = this;
12151
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
12152
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
12153
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
12154
11907
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
12155
- hls.on(Events.ERROR, this.onError, this);
12156
- }
12157
- unregisterListeners() {
12158
- const {
12159
- hls
12160
- } = this;
12161
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
12162
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
12163
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
12164
- hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
12165
- hls.off(Events.ERROR, this.onError, this);
12166
11908
  }
12167
11909
  doTick() {
12168
11910
  this.onTickEnd();
@@ -12186,12 +11928,6 @@ class BaseStreamController extends TaskLoop {
12186
11928
  this.clearNextTick();
12187
11929
  this.state = State.STOPPED;
12188
11930
  }
12189
- pauseBuffering() {
12190
- this.buffering = false;
12191
- }
12192
- resumeBuffering() {
12193
- this.buffering = true;
12194
- }
12195
11931
  _streamEnded(bufferInfo, levelDetails) {
12196
11932
  // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
12197
11933
  // of nothing loading/loaded return false
@@ -12222,8 +11958,10 @@ class BaseStreamController extends TaskLoop {
12222
11958
  }
12223
11959
  onMediaAttached(event, data) {
12224
11960
  const media = this.media = this.mediaBuffer = data.media;
12225
- media.addEventListener('seeking', this.onMediaSeeking);
12226
- media.addEventListener('ended', this.onMediaEnded);
11961
+ this.onvseeking = this.onMediaSeeking.bind(this);
11962
+ this.onvended = this.onMediaEnded.bind(this);
11963
+ media.addEventListener('seeking', this.onvseeking);
11964
+ media.addEventListener('ended', this.onvended);
12227
11965
  const config = this.config;
12228
11966
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
12229
11967
  this.startLoad(config.startPosition);
@@ -12237,9 +11975,10 @@ class BaseStreamController extends TaskLoop {
12237
11975
  }
12238
11976
 
12239
11977
  // remove video listeners
12240
- if (media) {
12241
- media.removeEventListener('seeking', this.onMediaSeeking);
12242
- media.removeEventListener('ended', this.onMediaEnded);
11978
+ if (media && this.onvseeking && this.onvended) {
11979
+ media.removeEventListener('seeking', this.onvseeking);
11980
+ media.removeEventListener('ended', this.onvended);
11981
+ this.onvseeking = this.onvended = null;
12243
11982
  }
12244
11983
  if (this.keyLoader) {
12245
11984
  this.keyLoader.detach();
@@ -12249,8 +11988,56 @@ class BaseStreamController extends TaskLoop {
12249
11988
  this.fragmentTracker.removeAllFragments();
12250
11989
  this.stopLoad();
12251
11990
  }
12252
- onManifestLoading() {}
12253
- onError(event, data) {}
11991
+ onMediaSeeking() {
11992
+ const {
11993
+ config,
11994
+ fragCurrent,
11995
+ media,
11996
+ mediaBuffer,
11997
+ state
11998
+ } = this;
11999
+ const currentTime = media ? media.currentTime : 0;
12000
+ const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
12001
+ this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
12002
+ if (this.state === State.ENDED) {
12003
+ this.resetLoadingState();
12004
+ } else if (fragCurrent) {
12005
+ // Seeking while frag load is in progress
12006
+ const tolerance = config.maxFragLookUpTolerance;
12007
+ const fragStartOffset = fragCurrent.start - tolerance;
12008
+ const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
12009
+ // if seeking out of buffered range or into new one
12010
+ if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
12011
+ const pastFragment = currentTime > fragEndOffset;
12012
+ // if the seek position is outside the current fragment range
12013
+ if (currentTime < fragStartOffset || pastFragment) {
12014
+ if (pastFragment && fragCurrent.loader) {
12015
+ this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
12016
+ fragCurrent.abortRequests();
12017
+ this.resetLoadingState();
12018
+ }
12019
+ this.fragPrevious = null;
12020
+ }
12021
+ }
12022
+ }
12023
+ if (media) {
12024
+ // Remove gap fragments
12025
+ this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
12026
+ this.lastCurrentTime = currentTime;
12027
+ }
12028
+
12029
+ // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
12030
+ if (!this.loadedmetadata && !bufferInfo.len) {
12031
+ this.nextLoadPosition = this.startPosition = currentTime;
12032
+ }
12033
+
12034
+ // Async tick to speed up processing
12035
+ this.tickImmediate();
12036
+ }
12037
+ onMediaEnded() {
12038
+ // reset startPosition and lastCurrentTime to restart playback @ stream beginning
12039
+ this.startPosition = this.lastCurrentTime = 0;
12040
+ }
12254
12041
  onManifestLoaded(event, data) {
12255
12042
  this.startTimeOffset = data.startTimeOffset;
12256
12043
  this.initPTS = [];
@@ -12260,7 +12047,7 @@ class BaseStreamController extends TaskLoop {
12260
12047
  this.stopLoad();
12261
12048
  super.onHandlerDestroying();
12262
12049
  // @ts-ignore
12263
- this.hls = this.onMediaSeeking = this.onMediaEnded = null;
12050
+ this.hls = null;
12264
12051
  }
12265
12052
  onHandlerDestroyed() {
12266
12053
  this.state = State.STOPPED;
@@ -12391,10 +12178,10 @@ class BaseStreamController extends TaskLoop {
12391
12178
  const decryptData = frag.decryptdata;
12392
12179
 
12393
12180
  // check to see if the payload needs to be decrypted
12394
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
12181
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
12395
12182
  const startTime = self.performance.now();
12396
12183
  // decrypt init segment data
12397
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
12184
+ return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
12398
12185
  hls.trigger(Events.ERROR, {
12399
12186
  type: ErrorTypes.MEDIA_ERROR,
12400
12187
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -12506,7 +12293,7 @@ class BaseStreamController extends TaskLoop {
12506
12293
  }
12507
12294
  let keyLoadingPromise = null;
12508
12295
  if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
12509
- this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
12296
+ this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
12510
12297
  this.state = State.KEY_LOADING;
12511
12298
  this.fragCurrent = frag;
12512
12299
  keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
@@ -12527,16 +12314,8 @@ class BaseStreamController extends TaskLoop {
12527
12314
  } else if (!frag.encrypted && details.encryptedFragments.length) {
12528
12315
  this.keyLoader.loadClear(frag, details.encryptedFragments);
12529
12316
  }
12530
- const fragPrevious = this.fragPrevious;
12531
- if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
12532
- const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
12533
- if (shouldLoadParts !== this.loadingParts) {
12534
- this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
12535
- this.loadingParts = shouldLoadParts;
12536
- }
12537
- }
12538
12317
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
12539
- if (this.loadingParts && frag.sn !== 'initSegment') {
12318
+ if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
12540
12319
  const partList = details.partList;
12541
12320
  if (partList && progressCallback) {
12542
12321
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -12545,7 +12324,7 @@ class BaseStreamController extends TaskLoop {
12545
12324
  const partIndex = this.getNextPart(partList, frag, targetBufferTime);
12546
12325
  if (partIndex > -1) {
12547
12326
  const part = partList[partIndex];
12548
- this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
12327
+ this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
12549
12328
  this.nextLoadPosition = part.start + part.duration;
12550
12329
  this.state = State.FRAG_LOADING;
12551
12330
  let _result;
@@ -12574,14 +12353,7 @@ class BaseStreamController extends TaskLoop {
12574
12353
  }
12575
12354
  }
12576
12355
  }
12577
- if (frag.sn !== 'initSegment' && this.loadingParts) {
12578
- this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
12579
- this.loadingParts = false;
12580
- } else if (!frag.url) {
12581
- // Selected fragment hint for part but not loading parts
12582
- return Promise.resolve(null);
12583
- }
12584
- this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
12356
+ this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
12585
12357
  // Don't update nextLoadPosition for fragments which are not buffered
12586
12358
  if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
12587
12359
  this.nextLoadPosition = frag.start + frag.duration;
@@ -12679,36 +12451,8 @@ class BaseStreamController extends TaskLoop {
12679
12451
  if (part) {
12680
12452
  part.stats.parsing.end = now;
12681
12453
  }
12682
- // See if part loading should be disabled/enabled based on buffer and playback position.
12683
- if (frag.sn !== 'initSegment') {
12684
- const levelDetails = this.getLevelDetails();
12685
- const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
12686
- const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
12687
- if (shouldLoadParts !== this.loadingParts) {
12688
- this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
12689
- this.loadingParts = shouldLoadParts;
12690
- }
12691
- }
12692
12454
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
12693
12455
  }
12694
- shouldLoadParts(details, bufferEnd) {
12695
- if (this.config.lowLatencyMode) {
12696
- if (!details) {
12697
- return this.loadingParts;
12698
- }
12699
- if (details != null && details.partList) {
12700
- var _details$fragmentHint;
12701
- // Buffer must be ahead of first part + duration of parts after last segment
12702
- // and playback must be at or past segment adjacent to part list
12703
- const firstPart = details.partList[0];
12704
- const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
12705
- if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
12706
- return true;
12707
- }
12708
- }
12709
- }
12710
- return false;
12711
- }
12712
12456
  getCurrentContext(chunkMeta) {
12713
12457
  const {
12714
12458
  levels,
@@ -12809,7 +12553,7 @@ class BaseStreamController extends TaskLoop {
12809
12553
  // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
12810
12554
  if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
12811
12555
  const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
12812
- if (bufferedFragAtPos && (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)) {
12556
+ if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
12813
12557
  return BufferHelper.bufferInfo(bufferable, pos, Math.max(bufferInfo.nextStart, maxBufferHole));
12814
12558
  }
12815
12559
  }
@@ -12857,8 +12601,7 @@ class BaseStreamController extends TaskLoop {
12857
12601
  config
12858
12602
  } = this;
12859
12603
  const start = fragments[0].start;
12860
- const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
12861
- let frag = null;
12604
+ let frag;
12862
12605
  if (levelDetails.live) {
12863
12606
  const initialLiveManifestSize = config.initialLiveManifestSize;
12864
12607
  if (fragLen < initialLiveManifestSize) {
@@ -12870,10 +12613,6 @@ class BaseStreamController extends TaskLoop {
12870
12613
  // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
12871
12614
  // we get the fragment matching that start time
12872
12615
  if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
12873
- if (canLoadParts && !this.loadingParts) {
12874
- this.log(`LL-Part loading ON for initial live fragment`);
12875
- this.loadingParts = true;
12876
- }
12877
12616
  frag = this.getInitialLiveFragment(levelDetails, fragments);
12878
12617
  this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
12879
12618
  }
@@ -12884,7 +12623,7 @@ class BaseStreamController extends TaskLoop {
12884
12623
 
12885
12624
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
12886
12625
  if (!frag) {
12887
- const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
12626
+ const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
12888
12627
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
12889
12628
  }
12890
12629
  return this.mapToInitFragWhenRequired(frag);
@@ -13006,7 +12745,7 @@ class BaseStreamController extends TaskLoop {
13006
12745
  } = levelDetails;
13007
12746
  const tolerance = config.maxFragLookUpTolerance;
13008
12747
  const partList = levelDetails.partList;
13009
- const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
12748
+ const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
13010
12749
  if (loadingParts && fragmentHint && !this.bitrateTest) {
13011
12750
  // Include incomplete fragment with parts at end
13012
12751
  fragments = fragments.concat(fragmentHint);
@@ -13199,7 +12938,7 @@ class BaseStreamController extends TaskLoop {
13199
12938
  errorAction.resolved = true;
13200
12939
  }
13201
12940
  } else {
13202
- this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
12941
+ logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
13203
12942
  return;
13204
12943
  }
13205
12944
  } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
@@ -13267,9 +13006,7 @@ class BaseStreamController extends TaskLoop {
13267
13006
  this.log('Reset loading state');
13268
13007
  this.fragCurrent = null;
13269
13008
  this.fragPrevious = null;
13270
- if (this.state !== State.STOPPED) {
13271
- this.state = State.IDLE;
13272
- }
13009
+ this.state = State.IDLE;
13273
13010
  }
13274
13011
  resetStartWhenNotLoaded(level) {
13275
13012
  // if loadedmetadata is not set, it means that first frag request failed
@@ -13596,7 +13333,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
13596
13333
  */
13597
13334
  function getAudioConfig(observer, data, offset, audioCodec) {
13598
13335
  let adtsObjectType;
13599
- let originalAdtsObjectType;
13600
13336
  let adtsExtensionSamplingIndex;
13601
13337
  let adtsChannelConfig;
13602
13338
  let config;
@@ -13604,7 +13340,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13604
13340
  const manifestCodec = audioCodec;
13605
13341
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
13606
13342
  // byte 2
13607
- adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
13343
+ adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
13608
13344
  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
13609
13345
  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
13610
13346
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
@@ -13621,8 +13357,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13621
13357
  // byte 3
13622
13358
  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
13623
13359
  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
13624
- // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
13625
- if (/firefox|palemoon/i.test(userAgent)) {
13360
+ // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
13361
+ if (/firefox/i.test(userAgent)) {
13626
13362
  if (adtsSamplingIndex >= 6) {
13627
13363
  adtsObjectType = 5;
13628
13364
  config = new Array(4);
@@ -13716,7 +13452,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13716
13452
  samplerate: adtsSamplingRates[adtsSamplingIndex],
13717
13453
  channelCount: adtsChannelConfig,
13718
13454
  codec: 'mp4a.40.' + adtsObjectType,
13719
- parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
13720
13455
  manifestCodec
13721
13456
  };
13722
13457
  }
@@ -13771,8 +13506,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
13771
13506
  track.channelCount = config.channelCount;
13772
13507
  track.codec = config.codec;
13773
13508
  track.manifestCodec = config.manifestCodec;
13774
- track.parsedCodec = config.parsedCodec;
13775
- logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
13509
+ logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
13776
13510
  }
13777
13511
  }
13778
13512
  function getFrameDuration(samplerate) {
@@ -14250,110 +13984,6 @@ class BaseVideoParser {
14250
13984
  logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
14251
13985
  }
14252
13986
  }
14253
- parseNALu(track, array) {
14254
- const len = array.byteLength;
14255
- let state = track.naluState || 0;
14256
- const lastState = state;
14257
- const units = [];
14258
- let i = 0;
14259
- let value;
14260
- let overflow;
14261
- let unitType;
14262
- let lastUnitStart = -1;
14263
- let lastUnitType = 0;
14264
- // logger.log('PES:' + Hex.hexDump(array));
14265
-
14266
- if (state === -1) {
14267
- // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
14268
- lastUnitStart = 0;
14269
- // NALu type is value read from offset 0
14270
- lastUnitType = this.getNALuType(array, 0);
14271
- state = 0;
14272
- i = 1;
14273
- }
14274
- while (i < len) {
14275
- value = array[i++];
14276
- // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
14277
- if (!state) {
14278
- state = value ? 0 : 1;
14279
- continue;
14280
- }
14281
- if (state === 1) {
14282
- state = value ? 0 : 2;
14283
- continue;
14284
- }
14285
- // here we have state either equal to 2 or 3
14286
- if (!value) {
14287
- state = 3;
14288
- } else if (value === 1) {
14289
- overflow = i - state - 1;
14290
- if (lastUnitStart >= 0) {
14291
- const unit = {
14292
- data: array.subarray(lastUnitStart, overflow),
14293
- type: lastUnitType
14294
- };
14295
- // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
14296
- units.push(unit);
14297
- } else {
14298
- // lastUnitStart is undefined => this is the first start code found in this PES packet
14299
- // first check if start code delimiter is overlapping between 2 PES packets,
14300
- // ie it started in last packet (lastState not zero)
14301
- // and ended at the beginning of this PES packet (i <= 4 - lastState)
14302
- const lastUnit = this.getLastNalUnit(track.samples);
14303
- if (lastUnit) {
14304
- if (lastState && i <= 4 - lastState) {
14305
- // start delimiter overlapping between PES packets
14306
- // strip start delimiter bytes from the end of last NAL unit
14307
- // check if lastUnit had a state different from zero
14308
- if (lastUnit.state) {
14309
- // strip last bytes
14310
- lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
14311
- }
14312
- }
14313
- // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
14314
-
14315
- if (overflow > 0) {
14316
- // logger.log('first NALU found with overflow:' + overflow);
14317
- lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
14318
- lastUnit.state = 0;
14319
- }
14320
- }
14321
- }
14322
- // check if we can read unit type
14323
- if (i < len) {
14324
- unitType = this.getNALuType(array, i);
14325
- // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
14326
- lastUnitStart = i;
14327
- lastUnitType = unitType;
14328
- state = 0;
14329
- } else {
14330
- // not enough byte to read unit type. let's read it on next PES parsing
14331
- state = -1;
14332
- }
14333
- } else {
14334
- state = 0;
14335
- }
14336
- }
14337
- if (lastUnitStart >= 0 && state >= 0) {
14338
- const unit = {
14339
- data: array.subarray(lastUnitStart, len),
14340
- type: lastUnitType,
14341
- state: state
14342
- };
14343
- units.push(unit);
14344
- // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
14345
- }
14346
- // no NALu found
14347
- if (units.length === 0) {
14348
- // append pes.data to previous NAL unit
14349
- const lastUnit = this.getLastNalUnit(track.samples);
14350
- if (lastUnit) {
14351
- lastUnit.data = appendUint8Array(lastUnit.data, array);
14352
- }
14353
- }
14354
- track.naluState = state;
14355
- return units;
14356
- }
14357
13987
  }
14358
13988
 
14359
13989
  /**
@@ -14496,11 +14126,194 @@ class ExpGolomb {
14496
14126
  readUInt() {
14497
14127
  return this.readBits(32);
14498
14128
  }
14129
+
14130
+ /**
14131
+ * Advance the ExpGolomb decoder past a scaling list. The scaling
14132
+ * list is optionally transmitted as part of a sequence parameter
14133
+ * set and is not relevant to transmuxing.
14134
+ * @param count the number of entries in this scaling list
14135
+ * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
14136
+ */
14137
+ skipScalingList(count) {
14138
+ let lastScale = 8;
14139
+ let nextScale = 8;
14140
+ let deltaScale;
14141
+ for (let j = 0; j < count; j++) {
14142
+ if (nextScale !== 0) {
14143
+ deltaScale = this.readEG();
14144
+ nextScale = (lastScale + deltaScale + 256) % 256;
14145
+ }
14146
+ lastScale = nextScale === 0 ? lastScale : nextScale;
14147
+ }
14148
+ }
14149
+
14150
+ /**
14151
+ * Read a sequence parameter set and return some interesting video
14152
+ * properties. A sequence parameter set is the H264 metadata that
14153
+ * describes the properties of upcoming video frames.
14154
+ * @returns an object with configuration parsed from the
14155
+ * sequence parameter set, including the dimensions of the
14156
+ * associated video frames.
14157
+ */
14158
+ readSPS() {
14159
+ let frameCropLeftOffset = 0;
14160
+ let frameCropRightOffset = 0;
14161
+ let frameCropTopOffset = 0;
14162
+ let frameCropBottomOffset = 0;
14163
+ let numRefFramesInPicOrderCntCycle;
14164
+ let scalingListCount;
14165
+ let i;
14166
+ const readUByte = this.readUByte.bind(this);
14167
+ const readBits = this.readBits.bind(this);
14168
+ const readUEG = this.readUEG.bind(this);
14169
+ const readBoolean = this.readBoolean.bind(this);
14170
+ const skipBits = this.skipBits.bind(this);
14171
+ const skipEG = this.skipEG.bind(this);
14172
+ const skipUEG = this.skipUEG.bind(this);
14173
+ const skipScalingList = this.skipScalingList.bind(this);
14174
+ readUByte();
14175
+ const profileIdc = readUByte(); // profile_idc
14176
+ readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
14177
+ skipBits(3); // reserved_zero_3bits u(3),
14178
+ readUByte(); // level_idc u(8)
14179
+ skipUEG(); // seq_parameter_set_id
14180
+ // some profiles have more optional data we don't need
14181
+ if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
14182
+ const chromaFormatIdc = readUEG();
14183
+ if (chromaFormatIdc === 3) {
14184
+ skipBits(1);
14185
+ } // separate_colour_plane_flag
14186
+
14187
+ skipUEG(); // bit_depth_luma_minus8
14188
+ skipUEG(); // bit_depth_chroma_minus8
14189
+ skipBits(1); // qpprime_y_zero_transform_bypass_flag
14190
+ if (readBoolean()) {
14191
+ // seq_scaling_matrix_present_flag
14192
+ scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
14193
+ for (i = 0; i < scalingListCount; i++) {
14194
+ if (readBoolean()) {
14195
+ // seq_scaling_list_present_flag[ i ]
14196
+ if (i < 6) {
14197
+ skipScalingList(16);
14198
+ } else {
14199
+ skipScalingList(64);
14200
+ }
14201
+ }
14202
+ }
14203
+ }
14204
+ }
14205
+ skipUEG(); // log2_max_frame_num_minus4
14206
+ const picOrderCntType = readUEG();
14207
+ if (picOrderCntType === 0) {
14208
+ readUEG(); // log2_max_pic_order_cnt_lsb_minus4
14209
+ } else if (picOrderCntType === 1) {
14210
+ skipBits(1); // delta_pic_order_always_zero_flag
14211
+ skipEG(); // offset_for_non_ref_pic
14212
+ skipEG(); // offset_for_top_to_bottom_field
14213
+ numRefFramesInPicOrderCntCycle = readUEG();
14214
+ for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
14215
+ skipEG();
14216
+ } // offset_for_ref_frame[ i ]
14217
+ }
14218
+ skipUEG(); // max_num_ref_frames
14219
+ skipBits(1); // gaps_in_frame_num_value_allowed_flag
14220
+ const picWidthInMbsMinus1 = readUEG();
14221
+ const picHeightInMapUnitsMinus1 = readUEG();
14222
+ const frameMbsOnlyFlag = readBits(1);
14223
+ if (frameMbsOnlyFlag === 0) {
14224
+ skipBits(1);
14225
+ } // mb_adaptive_frame_field_flag
14226
+
14227
+ skipBits(1); // direct_8x8_inference_flag
14228
+ if (readBoolean()) {
14229
+ // frame_cropping_flag
14230
+ frameCropLeftOffset = readUEG();
14231
+ frameCropRightOffset = readUEG();
14232
+ frameCropTopOffset = readUEG();
14233
+ frameCropBottomOffset = readUEG();
14234
+ }
14235
+ let pixelRatio = [1, 1];
14236
+ if (readBoolean()) {
14237
+ // vui_parameters_present_flag
14238
+ if (readBoolean()) {
14239
+ // aspect_ratio_info_present_flag
14240
+ const aspectRatioIdc = readUByte();
14241
+ switch (aspectRatioIdc) {
14242
+ case 1:
14243
+ pixelRatio = [1, 1];
14244
+ break;
14245
+ case 2:
14246
+ pixelRatio = [12, 11];
14247
+ break;
14248
+ case 3:
14249
+ pixelRatio = [10, 11];
14250
+ break;
14251
+ case 4:
14252
+ pixelRatio = [16, 11];
14253
+ break;
14254
+ case 5:
14255
+ pixelRatio = [40, 33];
14256
+ break;
14257
+ case 6:
14258
+ pixelRatio = [24, 11];
14259
+ break;
14260
+ case 7:
14261
+ pixelRatio = [20, 11];
14262
+ break;
14263
+ case 8:
14264
+ pixelRatio = [32, 11];
14265
+ break;
14266
+ case 9:
14267
+ pixelRatio = [80, 33];
14268
+ break;
14269
+ case 10:
14270
+ pixelRatio = [18, 11];
14271
+ break;
14272
+ case 11:
14273
+ pixelRatio = [15, 11];
14274
+ break;
14275
+ case 12:
14276
+ pixelRatio = [64, 33];
14277
+ break;
14278
+ case 13:
14279
+ pixelRatio = [160, 99];
14280
+ break;
14281
+ case 14:
14282
+ pixelRatio = [4, 3];
14283
+ break;
14284
+ case 15:
14285
+ pixelRatio = [3, 2];
14286
+ break;
14287
+ case 16:
14288
+ pixelRatio = [2, 1];
14289
+ break;
14290
+ case 255:
14291
+ {
14292
+ pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
14293
+ break;
14294
+ }
14295
+ }
14296
+ }
14297
+ }
14298
+ return {
14299
+ width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
14300
+ height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
14301
+ pixelRatio: pixelRatio
14302
+ };
14303
+ }
14304
+ readSliceType() {
14305
+ // skip NALu type
14306
+ this.readUByte();
14307
+ // discard first_mb_in_slice
14308
+ this.readUEG();
14309
+ // return slice_type
14310
+ return this.readUEG();
14311
+ }
14499
14312
  }
14500
14313
 
14501
14314
  class AvcVideoParser extends BaseVideoParser {
14502
- parsePES(track, textTrack, pes, last, duration) {
14503
- const units = this.parseNALu(track, pes.data);
14315
+ parseAVCPES(track, textTrack, pes, last, duration) {
14316
+ const units = this.parseAVCNALu(track, pes.data);
14504
14317
  let VideoSample = this.VideoSample;
14505
14318
  let push;
14506
14319
  let spsfound = false;
@@ -14525,7 +14338,7 @@ class AvcVideoParser extends BaseVideoParser {
14525
14338
  // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
14526
14339
  if (spsfound && data.length > 4) {
14527
14340
  // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
14528
- const sliceType = this.readSliceType(data);
14341
+ const sliceType = new ExpGolomb(data).readSliceType();
14529
14342
  // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
14530
14343
  // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
14531
14344
  // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
@@ -14579,7 +14392,8 @@ class AvcVideoParser extends BaseVideoParser {
14579
14392
  push = true;
14580
14393
  spsfound = true;
14581
14394
  const sps = unit.data;
14582
- const config = this.readSPS(sps);
14395
+ const expGolombDecoder = new ExpGolomb(sps);
14396
+ const config = expGolombDecoder.readSPS();
14583
14397
  if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) {
14584
14398
  track.width = config.width;
14585
14399
  track.height = config.height;
@@ -14635,192 +14449,109 @@ class AvcVideoParser extends BaseVideoParser {
14635
14449
  this.VideoSample = null;
14636
14450
  }
14637
14451
  }
14638
- getNALuType(data, offset) {
14639
- return data[offset] & 0x1f;
14640
- }
14641
- readSliceType(data) {
14642
- const eg = new ExpGolomb(data);
14643
- // skip NALu type
14644
- eg.readUByte();
14645
- // discard first_mb_in_slice
14646
- eg.readUEG();
14647
- // return slice_type
14648
- return eg.readUEG();
14649
- }
14452
+ parseAVCNALu(track, array) {
14453
+ const len = array.byteLength;
14454
+ let state = track.naluState || 0;
14455
+ const lastState = state;
14456
+ const units = [];
14457
+ let i = 0;
14458
+ let value;
14459
+ let overflow;
14460
+ let unitType;
14461
+ let lastUnitStart = -1;
14462
+ let lastUnitType = 0;
14463
+ // logger.log('PES:' + Hex.hexDump(array));
14650
14464
 
14651
- /**
14652
- * The scaling list is optionally transmitted as part of a sequence parameter
14653
- * set and is not relevant to transmuxing.
14654
- * @param count the number of entries in this scaling list
14655
- * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
14656
- */
14657
- skipScalingList(count, reader) {
14658
- let lastScale = 8;
14659
- let nextScale = 8;
14660
- let deltaScale;
14661
- for (let j = 0; j < count; j++) {
14662
- if (nextScale !== 0) {
14663
- deltaScale = reader.readEG();
14664
- nextScale = (lastScale + deltaScale + 256) % 256;
14665
- }
14666
- lastScale = nextScale === 0 ? lastScale : nextScale;
14465
+ if (state === -1) {
14466
+ // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
14467
+ lastUnitStart = 0;
14468
+ // NALu type is value read from offset 0
14469
+ lastUnitType = array[0] & 0x1f;
14470
+ state = 0;
14471
+ i = 1;
14667
14472
  }
14668
- }
14669
-
14670
- /**
14671
- * Read a sequence parameter set and return some interesting video
14672
- * properties. A sequence parameter set is the H264 metadata that
14673
- * describes the properties of upcoming video frames.
14674
- * @returns an object with configuration parsed from the
14675
- * sequence parameter set, including the dimensions of the
14676
- * associated video frames.
14677
- */
14678
- readSPS(sps) {
14679
- const eg = new ExpGolomb(sps);
14680
- let frameCropLeftOffset = 0;
14681
- let frameCropRightOffset = 0;
14682
- let frameCropTopOffset = 0;
14683
- let frameCropBottomOffset = 0;
14684
- let numRefFramesInPicOrderCntCycle;
14685
- let scalingListCount;
14686
- let i;
14687
- const readUByte = eg.readUByte.bind(eg);
14688
- const readBits = eg.readBits.bind(eg);
14689
- const readUEG = eg.readUEG.bind(eg);
14690
- const readBoolean = eg.readBoolean.bind(eg);
14691
- const skipBits = eg.skipBits.bind(eg);
14692
- const skipEG = eg.skipEG.bind(eg);
14693
- const skipUEG = eg.skipUEG.bind(eg);
14694
- const skipScalingList = this.skipScalingList.bind(this);
14695
- readUByte();
14696
- const profileIdc = readUByte(); // profile_idc
14697
- readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
14698
- skipBits(3); // reserved_zero_3bits u(3),
14699
- readUByte(); // level_idc u(8)
14700
- skipUEG(); // seq_parameter_set_id
14701
- // some profiles have more optional data we don't need
14702
- if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
14703
- const chromaFormatIdc = readUEG();
14704
- if (chromaFormatIdc === 3) {
14705
- skipBits(1);
14706
- } // separate_colour_plane_flag
14473
+ while (i < len) {
14474
+ value = array[i++];
14475
+ // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
14476
+ if (!state) {
14477
+ state = value ? 0 : 1;
14478
+ continue;
14479
+ }
14480
+ if (state === 1) {
14481
+ state = value ? 0 : 2;
14482
+ continue;
14483
+ }
14484
+ // here we have state either equal to 2 or 3
14485
+ if (!value) {
14486
+ state = 3;
14487
+ } else if (value === 1) {
14488
+ overflow = i - state - 1;
14489
+ if (lastUnitStart >= 0) {
14490
+ const unit = {
14491
+ data: array.subarray(lastUnitStart, overflow),
14492
+ type: lastUnitType
14493
+ };
14494
+ // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
14495
+ units.push(unit);
14496
+ } else {
14497
+ // lastUnitStart is undefined => this is the first start code found in this PES packet
14498
+ // first check if start code delimiter is overlapping between 2 PES packets,
14499
+ // ie it started in last packet (lastState not zero)
14500
+ // and ended at the beginning of this PES packet (i <= 4 - lastState)
14501
+ const lastUnit = this.getLastNalUnit(track.samples);
14502
+ if (lastUnit) {
14503
+ if (lastState && i <= 4 - lastState) {
14504
+ // start delimiter overlapping between PES packets
14505
+ // strip start delimiter bytes from the end of last NAL unit
14506
+ // check if lastUnit had a state different from zero
14507
+ if (lastUnit.state) {
14508
+ // strip last bytes
14509
+ lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
14510
+ }
14511
+ }
14512
+ // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
14707
14513
 
14708
- skipUEG(); // bit_depth_luma_minus8
14709
- skipUEG(); // bit_depth_chroma_minus8
14710
- skipBits(1); // qpprime_y_zero_transform_bypass_flag
14711
- if (readBoolean()) {
14712
- // seq_scaling_matrix_present_flag
14713
- scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
14714
- for (i = 0; i < scalingListCount; i++) {
14715
- if (readBoolean()) {
14716
- // seq_scaling_list_present_flag[ i ]
14717
- if (i < 6) {
14718
- skipScalingList(16, eg);
14719
- } else {
14720
- skipScalingList(64, eg);
14514
+ if (overflow > 0) {
14515
+ // logger.log('first NALU found with overflow:' + overflow);
14516
+ lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
14517
+ lastUnit.state = 0;
14721
14518
  }
14722
14519
  }
14723
14520
  }
14521
+ // check if we can read unit type
14522
+ if (i < len) {
14523
+ unitType = array[i] & 0x1f;
14524
+ // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
14525
+ lastUnitStart = i;
14526
+ lastUnitType = unitType;
14527
+ state = 0;
14528
+ } else {
14529
+ // not enough byte to read unit type. let's read it on next PES parsing
14530
+ state = -1;
14531
+ }
14532
+ } else {
14533
+ state = 0;
14724
14534
  }
14725
14535
  }
14726
- skipUEG(); // log2_max_frame_num_minus4
14727
- const picOrderCntType = readUEG();
14728
- if (picOrderCntType === 0) {
14729
- readUEG(); // log2_max_pic_order_cnt_lsb_minus4
14730
- } else if (picOrderCntType === 1) {
14731
- skipBits(1); // delta_pic_order_always_zero_flag
14732
- skipEG(); // offset_for_non_ref_pic
14733
- skipEG(); // offset_for_top_to_bottom_field
14734
- numRefFramesInPicOrderCntCycle = readUEG();
14735
- for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
14736
- skipEG();
14737
- } // offset_for_ref_frame[ i ]
14738
- }
14739
- skipUEG(); // max_num_ref_frames
14740
- skipBits(1); // gaps_in_frame_num_value_allowed_flag
14741
- const picWidthInMbsMinus1 = readUEG();
14742
- const picHeightInMapUnitsMinus1 = readUEG();
14743
- const frameMbsOnlyFlag = readBits(1);
14744
- if (frameMbsOnlyFlag === 0) {
14745
- skipBits(1);
14746
- } // mb_adaptive_frame_field_flag
14747
-
14748
- skipBits(1); // direct_8x8_inference_flag
14749
- if (readBoolean()) {
14750
- // frame_cropping_flag
14751
- frameCropLeftOffset = readUEG();
14752
- frameCropRightOffset = readUEG();
14753
- frameCropTopOffset = readUEG();
14754
- frameCropBottomOffset = readUEG();
14536
+ if (lastUnitStart >= 0 && state >= 0) {
14537
+ const unit = {
14538
+ data: array.subarray(lastUnitStart, len),
14539
+ type: lastUnitType,
14540
+ state: state
14541
+ };
14542
+ units.push(unit);
14543
+ // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
14755
14544
  }
14756
- let pixelRatio = [1, 1];
14757
- if (readBoolean()) {
14758
- // vui_parameters_present_flag
14759
- if (readBoolean()) {
14760
- // aspect_ratio_info_present_flag
14761
- const aspectRatioIdc = readUByte();
14762
- switch (aspectRatioIdc) {
14763
- case 1:
14764
- pixelRatio = [1, 1];
14765
- break;
14766
- case 2:
14767
- pixelRatio = [12, 11];
14768
- break;
14769
- case 3:
14770
- pixelRatio = [10, 11];
14771
- break;
14772
- case 4:
14773
- pixelRatio = [16, 11];
14774
- break;
14775
- case 5:
14776
- pixelRatio = [40, 33];
14777
- break;
14778
- case 6:
14779
- pixelRatio = [24, 11];
14780
- break;
14781
- case 7:
14782
- pixelRatio = [20, 11];
14783
- break;
14784
- case 8:
14785
- pixelRatio = [32, 11];
14786
- break;
14787
- case 9:
14788
- pixelRatio = [80, 33];
14789
- break;
14790
- case 10:
14791
- pixelRatio = [18, 11];
14792
- break;
14793
- case 11:
14794
- pixelRatio = [15, 11];
14795
- break;
14796
- case 12:
14797
- pixelRatio = [64, 33];
14798
- break;
14799
- case 13:
14800
- pixelRatio = [160, 99];
14801
- break;
14802
- case 14:
14803
- pixelRatio = [4, 3];
14804
- break;
14805
- case 15:
14806
- pixelRatio = [3, 2];
14807
- break;
14808
- case 16:
14809
- pixelRatio = [2, 1];
14810
- break;
14811
- case 255:
14812
- {
14813
- pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
14814
- break;
14815
- }
14816
- }
14545
+ // no NALu found
14546
+ if (units.length === 0) {
14547
+ // append pes.data to previous NAL unit
14548
+ const lastUnit = this.getLastNalUnit(track.samples);
14549
+ if (lastUnit) {
14550
+ lastUnit.data = appendUint8Array(lastUnit.data, array);
14817
14551
  }
14818
14552
  }
14819
- return {
14820
- width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
14821
- height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
14822
- pixelRatio: pixelRatio
14823
- };
14553
+ track.naluState = state;
14554
+ return units;
14824
14555
  }
14825
14556
  }
14826
14557
 
@@ -14838,7 +14569,7 @@ class SampleAesDecrypter {
14838
14569
  });
14839
14570
  }
14840
14571
  decryptBuffer(encryptedData) {
14841
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
14572
+ return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
14842
14573
  }
14843
14574
 
14844
14575
  // AAC - encrypt all full 16 bytes blocks starting from offset 16
@@ -14952,7 +14683,7 @@ class TSDemuxer {
14952
14683
  this.observer = observer;
14953
14684
  this.config = config;
14954
14685
  this.typeSupported = typeSupported;
14955
- this.videoParser = null;
14686
+ this.videoParser = new AvcVideoParser();
14956
14687
  }
14957
14688
  static probe(data) {
14958
14689
  const syncOffset = TSDemuxer.syncOffset(data);
@@ -15117,16 +14848,7 @@ class TSDemuxer {
15117
14848
  case videoPid:
15118
14849
  if (stt) {
15119
14850
  if (videoData && (pes = parsePES(videoData))) {
15120
- if (this.videoParser === null) {
15121
- switch (videoTrack.segmentCodec) {
15122
- case 'avc':
15123
- this.videoParser = new AvcVideoParser();
15124
- break;
15125
- }
15126
- }
15127
- if (this.videoParser !== null) {
15128
- this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
15129
- }
14851
+ this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
15130
14852
  }
15131
14853
  videoData = {
15132
14854
  data: [],
@@ -15288,17 +15010,8 @@ class TSDemuxer {
15288
15010
  // try to parse last PES packets
15289
15011
  let pes;
15290
15012
  if (videoData && (pes = parsePES(videoData))) {
15291
- if (this.videoParser === null) {
15292
- switch (videoTrack.segmentCodec) {
15293
- case 'avc':
15294
- this.videoParser = new AvcVideoParser();
15295
- break;
15296
- }
15297
- }
15298
- if (this.videoParser !== null) {
15299
- this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
15300
- videoTrack.pesData = null;
15301
- }
15013
+ this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
15014
+ videoTrack.pesData = null;
15302
15015
  } else {
15303
15016
  // either avcData null or PES truncated, keep it for next frag parsing
15304
15017
  videoTrack.pesData = videoData;
@@ -15601,10 +15314,7 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
15601
15314
  logger.warn('Unsupported EC-3 in M2TS found');
15602
15315
  break;
15603
15316
  case 0x24:
15604
- // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
15605
- {
15606
- logger.warn('Unsupported HEVC in M2TS found');
15607
- }
15317
+ logger.warn('Unsupported HEVC in M2TS found');
15608
15318
  break;
15609
15319
  }
15610
15320
  // move to the next table entry
@@ -15827,8 +15537,6 @@ class MP4 {
15827
15537
  avc1: [],
15828
15538
  // codingname
15829
15539
  avcC: [],
15830
- hvc1: [],
15831
- hvcC: [],
15832
15540
  btrt: [],
15833
15541
  dinf: [],
15834
15542
  dref: [],
@@ -16253,10 +15961,8 @@ class MP4 {
16253
15961
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
16254
15962
  }
16255
15963
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
16256
- } else if (track.segmentCodec === 'avc') {
16257
- return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
16258
15964
  } else {
16259
- return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
15965
+ return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
16260
15966
  }
16261
15967
  }
16262
15968
  static tkhd(track) {
@@ -16394,84 +16100,6 @@ class MP4 {
16394
16100
  const result = appendUint8Array(MP4.FTYP, movie);
16395
16101
  return result;
16396
16102
  }
16397
- static hvc1(track) {
16398
- const ps = track.params;
16399
- const units = [track.vps, track.sps, track.pps];
16400
- const NALuLengthSize = 4;
16401
- const config = new Uint8Array([0x01, ps.general_profile_space << 6 | (ps.general_tier_flag ? 32 : 0) | ps.general_profile_idc, ps.general_profile_compatibility_flags[0], ps.general_profile_compatibility_flags[1], ps.general_profile_compatibility_flags[2], ps.general_profile_compatibility_flags[3], ps.general_constraint_indicator_flags[0], ps.general_constraint_indicator_flags[1], ps.general_constraint_indicator_flags[2], ps.general_constraint_indicator_flags[3], ps.general_constraint_indicator_flags[4], ps.general_constraint_indicator_flags[5], ps.general_level_idc, 240 | ps.min_spatial_segmentation_idc >> 8, 255 & ps.min_spatial_segmentation_idc, 252 | ps.parallelismType, 252 | ps.chroma_format_idc, 248 | ps.bit_depth_luma_minus8, 248 | ps.bit_depth_chroma_minus8, 0x00, parseInt(ps.frame_rate.fps), NALuLengthSize - 1 | ps.temporal_id_nested << 2 | ps.num_temporal_layers << 3 | (ps.frame_rate.fixed ? 64 : 0), units.length]);
16402
-
16403
- // compute hvcC size in bytes
16404
- let length = config.length;
16405
- for (let i = 0; i < units.length; i += 1) {
16406
- length += 3;
16407
- for (let j = 0; j < units[i].length; j += 1) {
16408
- length += 2 + units[i][j].length;
16409
- }
16410
- }
16411
- const hvcC = new Uint8Array(length);
16412
- hvcC.set(config, 0);
16413
- length = config.length;
16414
- // append parameter set units: one vps, one or more sps and pps
16415
- const iMax = units.length - 1;
16416
- for (let i = 0; i < units.length; i += 1) {
16417
- hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
16418
- length += 3;
16419
- for (let j = 0; j < units[i].length; j += 1) {
16420
- hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
16421
- length += 2;
16422
- hvcC.set(units[i][j], length);
16423
- length += units[i][j].length;
16424
- }
16425
- }
16426
- const hvcc = MP4.box(MP4.types.hvcC, hvcC);
16427
- const width = track.width;
16428
- const height = track.height;
16429
- const hSpacing = track.pixelRatio[0];
16430
- const vSpacing = track.pixelRatio[1];
16431
- return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
16432
- // reserved
16433
- 0x00, 0x00, 0x00,
16434
- // reserved
16435
- 0x00, 0x01,
16436
- // data_reference_index
16437
- 0x00, 0x00,
16438
- // pre_defined
16439
- 0x00, 0x00,
16440
- // reserved
16441
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
16442
- // pre_defined
16443
- width >> 8 & 0xff, width & 0xff,
16444
- // width
16445
- height >> 8 & 0xff, height & 0xff,
16446
- // height
16447
- 0x00, 0x48, 0x00, 0x00,
16448
- // horizresolution
16449
- 0x00, 0x48, 0x00, 0x00,
16450
- // vertresolution
16451
- 0x00, 0x00, 0x00, 0x00,
16452
- // reserved
16453
- 0x00, 0x01,
16454
- // frame_count
16455
- 0x12, 0x64, 0x61, 0x69, 0x6c,
16456
- // dailymotion/hls.js
16457
- 0x79, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x68, 0x6c, 0x73, 0x2e, 0x6a, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
16458
- // compressorname
16459
- 0x00, 0x18,
16460
- // depth = 24
16461
- 0x11, 0x11]),
16462
- // pre_defined = -1
16463
- hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
16464
- // bufferSizeDB
16465
- 0x00, 0x2d, 0xc6, 0xc0,
16466
- // maxBitrate
16467
- 0x00, 0x2d, 0xc6, 0xc0])),
16468
- // avgBitrate
16469
- MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
16470
- // hSpacing
16471
- hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
16472
- // vSpacing
16473
- vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
16474
- }
16475
16103
  }
16476
16104
  MP4.types = void 0;
16477
16105
  MP4.HDLR_TYPES = void 0;
@@ -16847,9 +16475,9 @@ class MP4Remuxer {
16847
16475
  const foundOverlap = delta < -1;
16848
16476
  if (foundHole || foundOverlap) {
16849
16477
  if (foundHole) {
16850
- logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
16478
+ logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
16851
16479
  } else {
16852
- logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
16480
+ logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
16853
16481
  }
16854
16482
  if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
16855
16483
  firstDTS = nextAvcDts;
@@ -16858,24 +16486,12 @@ class MP4Remuxer {
16858
16486
  inputSamples[0].dts = firstDTS;
16859
16487
  inputSamples[0].pts = firstPTS;
16860
16488
  } else {
16861
- let isPTSOrderRetained = true;
16862
16489
  for (let i = 0; i < inputSamples.length; i++) {
16863
- if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
16490
+ if (inputSamples[i].dts > firstPTS) {
16864
16491
  break;
16865
16492
  }
16866
- const prevPTS = inputSamples[i].pts;
16867
16493
  inputSamples[i].dts -= delta;
16868
16494
  inputSamples[i].pts -= delta;
16869
-
16870
- // check to see if this sample's PTS order has changed
16871
- // relative to the next one
16872
- if (i < inputSamples.length - 1) {
16873
- const nextSamplePTS = inputSamples[i + 1].pts;
16874
- const currentSamplePTS = inputSamples[i].pts;
16875
- const currentOrder = nextSamplePTS <= currentSamplePTS;
16876
- const prevOrder = nextSamplePTS <= prevPTS;
16877
- isPTSOrderRetained = currentOrder == prevOrder;
16878
- }
16879
16495
  }
16880
16496
  }
16881
16497
  logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
@@ -17023,7 +16639,7 @@ class MP4Remuxer {
17023
16639
  }
17024
16640
  }
17025
16641
  }
17026
- // next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
16642
+ // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
17027
16643
  mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
17028
16644
  this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
17029
16645
  this.videoSampleDuration = mp4SampleDuration;
@@ -17156,7 +16772,7 @@ class MP4Remuxer {
17156
16772
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
17157
16773
  for (let j = 0; j < missing; j++) {
17158
16774
  const newStamp = Math.max(nextPts, 0);
17159
- let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
16775
+ let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
17160
16776
  if (!fillFrame) {
17161
16777
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
17162
16778
  fillFrame = sample.unit.subarray();
@@ -17284,7 +16900,7 @@ class MP4Remuxer {
17284
16900
  // samples count of this segment's duration
17285
16901
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
17286
16902
  // silent frame
17287
- const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
16903
+ const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
17288
16904
  logger.warn('[mp4-remuxer]: remux empty Audio');
17289
16905
  // Can't remux if we can't generate a silent frame...
17290
16906
  if (!silentFrame) {
@@ -17675,15 +17291,13 @@ class Transmuxer {
17675
17291
  initSegmentData
17676
17292
  } = transmuxConfig;
17677
17293
  const keyData = getEncryptionType(uintData, decryptdata);
17678
- if (keyData && isFullSegmentEncryption(keyData.method)) {
17294
+ if (keyData && keyData.method === 'AES-128') {
17679
17295
  const decrypter = this.getDecrypter();
17680
- const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
17681
-
17682
17296
  // Software decryption is synchronous; webCrypto is not
17683
17297
  if (decrypter.isSync()) {
17684
17298
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
17685
17299
  // data is handled in the flush() call
17686
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
17300
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
17687
17301
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
17688
17302
  const loadingParts = chunkMeta.part > -1;
17689
17303
  if (loadingParts) {
@@ -17695,7 +17309,7 @@ class Transmuxer {
17695
17309
  }
17696
17310
  uintData = new Uint8Array(decryptedData);
17697
17311
  } else {
17698
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
17312
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
17699
17313
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
17700
17314
  // the decrypted data has been transmuxed
17701
17315
  const result = this.push(decryptedData, null, chunkMeta);
@@ -18349,7 +17963,14 @@ class TransmuxerInterface {
18349
17963
  this.observer = new EventEmitter();
18350
17964
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
18351
17965
  this.observer.on(Events.ERROR, forwardMessage);
18352
- const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
17966
+ const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
17967
+ isTypeSupported: () => false
17968
+ };
17969
+ const m2tsTypeSupported = {
17970
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
17971
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
17972
+ ac3: false
17973
+ };
18353
17974
 
18354
17975
  // navigator.vendor is not always available in Web Worker
18355
17976
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -18613,9 +18234,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
18613
18234
  const MAX_START_GAP_JUMP = 2.0;
18614
18235
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
18615
18236
  const SKIP_BUFFER_RANGE_START = 0.05;
18616
- class GapController extends Logger {
18237
+ class GapController {
18617
18238
  constructor(config, media, fragmentTracker, hls) {
18618
- super('gap-controller', hls.logger);
18619
18239
  this.config = void 0;
18620
18240
  this.media = null;
18621
18241
  this.fragmentTracker = void 0;
@@ -18625,7 +18245,6 @@ class GapController extends Logger {
18625
18245
  this.stalled = null;
18626
18246
  this.moved = false;
18627
18247
  this.seeking = false;
18628
- this.ended = 0;
18629
18248
  this.config = config;
18630
18249
  this.media = media;
18631
18250
  this.fragmentTracker = fragmentTracker;
@@ -18643,7 +18262,7 @@ class GapController extends Logger {
18643
18262
  *
18644
18263
  * @param lastCurrentTime - Previously read playhead position
18645
18264
  */
18646
- poll(lastCurrentTime, activeFrag, levelDetails, state) {
18265
+ poll(lastCurrentTime, activeFrag) {
18647
18266
  const {
18648
18267
  config,
18649
18268
  media,
@@ -18662,7 +18281,6 @@ class GapController extends Logger {
18662
18281
 
18663
18282
  // The playhead is moving, no-op
18664
18283
  if (currentTime !== lastCurrentTime) {
18665
- this.ended = 0;
18666
18284
  this.moved = true;
18667
18285
  if (!seeking) {
18668
18286
  this.nudgeRetry = 0;
@@ -18671,7 +18289,7 @@ class GapController extends Logger {
18671
18289
  // The playhead is now moving, but was previously stalled
18672
18290
  if (this.stallReported) {
18673
18291
  const _stalledDuration = self.performance.now() - stalled;
18674
- this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
18292
+ logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
18675
18293
  this.stallReported = false;
18676
18294
  }
18677
18295
  this.stalled = null;
@@ -18707,6 +18325,7 @@ class GapController extends Logger {
18707
18325
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
18708
18326
  // The addition poll gives the browser a chance to jump the gap for us
18709
18327
  if (!this.moved && this.stalled !== null) {
18328
+ var _level$details;
18710
18329
  // There is no playable buffer (seeked, waiting for buffer)
18711
18330
  const isBuffered = bufferInfo.len > 0;
18712
18331
  if (!isBuffered && !nextStart) {
@@ -18718,8 +18337,9 @@ class GapController extends Logger {
18718
18337
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
18719
18338
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
18720
18339
  // that begins over 1 target duration after the video start position.
18721
- const isLive = !!(levelDetails != null && levelDetails.live);
18722
- const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
18340
+ const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
18341
+ const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
18342
+ const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
18723
18343
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
18724
18344
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
18725
18345
  if (!media.paused) {
@@ -18737,17 +18357,6 @@ class GapController extends Logger {
18737
18357
  }
18738
18358
  const stalledDuration = tnow - stalled;
18739
18359
  if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
18740
- // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
18741
- if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
18742
- if (stalledDuration < 1000 || this.ended) {
18743
- return;
18744
- }
18745
- this.ended = currentTime;
18746
- this.hls.trigger(Events.MEDIA_ENDED, {
18747
- stalled: true
18748
- });
18749
- return;
18750
- }
18751
18360
  // Report stalling after trying to fix
18752
18361
  this._reportStall(bufferInfo);
18753
18362
  if (!this.media) {
@@ -18791,7 +18400,7 @@ class GapController extends Logger {
18791
18400
  // needs to cross some sort of threshold covering all source-buffers content
18792
18401
  // to start playing properly.
18793
18402
  if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
18794
- this.warn('Trying to nudge playhead over buffer-hole');
18403
+ logger.warn('Trying to nudge playhead over buffer-hole');
18795
18404
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
18796
18405
  // We only try to jump the hole if it's under the configured size
18797
18406
  // Reset stalled so to rearm watchdog timer
@@ -18815,7 +18424,7 @@ class GapController extends Logger {
18815
18424
  // Report stalled error once
18816
18425
  this.stallReported = true;
18817
18426
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
18818
- this.warn(error.message);
18427
+ logger.warn(error.message);
18819
18428
  hls.trigger(Events.ERROR, {
18820
18429
  type: ErrorTypes.MEDIA_ERROR,
18821
18430
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -18883,7 +18492,7 @@ class GapController extends Logger {
18883
18492
  }
18884
18493
  }
18885
18494
  const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
18886
- this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
18495
+ logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
18887
18496
  this.moved = true;
18888
18497
  this.stalled = null;
18889
18498
  media.currentTime = targetTime;
@@ -18924,7 +18533,7 @@ class GapController extends Logger {
18924
18533
  const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
18925
18534
  // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
18926
18535
  const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
18927
- this.warn(error.message);
18536
+ logger.warn(error.message);
18928
18537
  media.currentTime = targetTime;
18929
18538
  hls.trigger(Events.ERROR, {
18930
18539
  type: ErrorTypes.MEDIA_ERROR,
@@ -18934,7 +18543,7 @@ class GapController extends Logger {
18934
18543
  });
18935
18544
  } else {
18936
18545
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
18937
- this.error(error.message);
18546
+ logger.error(error.message);
18938
18547
  hls.trigger(Events.ERROR, {
18939
18548
  type: ErrorTypes.MEDIA_ERROR,
18940
18549
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -18949,7 +18558,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
18949
18558
 
18950
18559
  class StreamController extends BaseStreamController {
18951
18560
  constructor(hls, fragmentTracker, keyLoader) {
18952
- super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
18561
+ super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
18953
18562
  this.audioCodecSwap = false;
18954
18563
  this.gapController = null;
18955
18564
  this.level = -1;
@@ -18957,43 +18566,27 @@ class StreamController extends BaseStreamController {
18957
18566
  this.altAudio = false;
18958
18567
  this.audioOnly = false;
18959
18568
  this.fragPlaying = null;
18569
+ this.onvplaying = null;
18570
+ this.onvseeked = null;
18960
18571
  this.fragLastKbps = 0;
18961
18572
  this.couldBacktrack = false;
18962
18573
  this.backtrackFragment = null;
18963
18574
  this.audioCodecSwitch = false;
18964
18575
  this.videoBuffer = null;
18965
- this.onMediaPlaying = () => {
18966
- // tick to speed up FRAG_CHANGED triggering
18967
- this.tick();
18968
- };
18969
- this.onMediaSeeked = () => {
18970
- const media = this.media;
18971
- const currentTime = media ? media.currentTime : null;
18972
- if (isFiniteNumber(currentTime)) {
18973
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
18974
- }
18975
-
18976
- // If seeked was issued before buffer was appended do not tick immediately
18977
- const bufferInfo = this.getMainFwdBufferInfo();
18978
- if (bufferInfo === null || bufferInfo.len === 0) {
18979
- this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
18980
- return;
18981
- }
18982
-
18983
- // tick to speed up FRAG_CHANGED triggering
18984
- this.tick();
18985
- };
18986
- this.registerListeners();
18576
+ this._registerListeners();
18987
18577
  }
18988
- registerListeners() {
18989
- super.registerListeners();
18578
+ _registerListeners() {
18990
18579
  const {
18991
18580
  hls
18992
18581
  } = this;
18582
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
18583
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
18584
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18993
18585
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18994
18586
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
18995
18587
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
18996
18588
  hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
18589
+ hls.on(Events.ERROR, this.onError, this);
18997
18590
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
18998
18591
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
18999
18592
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -19001,14 +18594,17 @@ class StreamController extends BaseStreamController {
19001
18594
  hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
19002
18595
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
19003
18596
  }
19004
- unregisterListeners() {
19005
- super.unregisterListeners();
18597
+ _unregisterListeners() {
19006
18598
  const {
19007
18599
  hls
19008
18600
  } = this;
18601
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
18602
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
18603
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
19009
18604
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
19010
18605
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
19011
18606
  hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
18607
+ hls.off(Events.ERROR, this.onError, this);
19012
18608
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
19013
18609
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
19014
18610
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -19017,9 +18613,7 @@ class StreamController extends BaseStreamController {
19017
18613
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
19018
18614
  }
19019
18615
  onHandlerDestroying() {
19020
- // @ts-ignore
19021
- this.onMediaPlaying = this.onMediaSeeked = null;
19022
- this.unregisterListeners();
18616
+ this._unregisterListeners();
19023
18617
  super.onHandlerDestroying();
19024
18618
  }
19025
18619
  startLoad(startPosition) {
@@ -19117,9 +18711,6 @@ class StreamController extends BaseStreamController {
19117
18711
  this.checkFragmentChanged();
19118
18712
  }
19119
18713
  doTickIdle() {
19120
- if (!this.buffering) {
19121
- return;
19122
- }
19123
18714
  const {
19124
18715
  hls,
19125
18716
  levelLastLoaded,
@@ -19347,17 +18938,20 @@ class StreamController extends BaseStreamController {
19347
18938
  onMediaAttached(event, data) {
19348
18939
  super.onMediaAttached(event, data);
19349
18940
  const media = data.media;
19350
- media.addEventListener('playing', this.onMediaPlaying);
19351
- media.addEventListener('seeked', this.onMediaSeeked);
18941
+ this.onvplaying = this.onMediaPlaying.bind(this);
18942
+ this.onvseeked = this.onMediaSeeked.bind(this);
18943
+ media.addEventListener('playing', this.onvplaying);
18944
+ media.addEventListener('seeked', this.onvseeked);
19352
18945
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
19353
18946
  }
19354
18947
  onMediaDetaching() {
19355
18948
  const {
19356
18949
  media
19357
18950
  } = this;
19358
- if (media) {
19359
- media.removeEventListener('playing', this.onMediaPlaying);
19360
- media.removeEventListener('seeked', this.onMediaSeeked);
18951
+ if (media && this.onvplaying && this.onvseeked) {
18952
+ media.removeEventListener('playing', this.onvplaying);
18953
+ media.removeEventListener('seeked', this.onvseeked);
18954
+ this.onvplaying = this.onvseeked = null;
19361
18955
  this.videoBuffer = null;
19362
18956
  }
19363
18957
  this.fragPlaying = null;
@@ -19367,6 +18961,27 @@ class StreamController extends BaseStreamController {
19367
18961
  }
19368
18962
  super.onMediaDetaching();
19369
18963
  }
18964
+ onMediaPlaying() {
18965
+ // tick to speed up FRAG_CHANGED triggering
18966
+ this.tick();
18967
+ }
18968
+ onMediaSeeked() {
18969
+ const media = this.media;
18970
+ const currentTime = media ? media.currentTime : null;
18971
+ if (isFiniteNumber(currentTime)) {
18972
+ this.log(`Media seeked to ${currentTime.toFixed(3)}`);
18973
+ }
18974
+
18975
+ // If seeked was issued before buffer was appended do not tick immediately
18976
+ const bufferInfo = this.getMainFwdBufferInfo();
18977
+ if (bufferInfo === null || bufferInfo.len === 0) {
18978
+ this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
18979
+ return;
18980
+ }
18981
+
18982
+ // tick to speed up FRAG_CHANGED triggering
18983
+ this.tick();
18984
+ }
19370
18985
  onManifestLoading() {
19371
18986
  // reset buffer on manifest loading
19372
18987
  this.log('Trigger BUFFER_RESET');
@@ -19658,10 +19273,8 @@ class StreamController extends BaseStreamController {
19658
19273
  }
19659
19274
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
19660
19275
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
19661
- const state = this.state;
19662
- const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
19663
- const levelDetails = this.getLevelDetails();
19664
- gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
19276
+ const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
19277
+ gapController.poll(this.lastCurrentTime, activeFrag);
19665
19278
  }
19666
19279
  this.lastCurrentTime = media.currentTime;
19667
19280
  }
@@ -19994,17 +19607,6 @@ class StreamController extends BaseStreamController {
19994
19607
  getMainFwdBufferInfo() {
19995
19608
  return this.getFwdBufferInfo(this.mediaBuffer ? this.mediaBuffer : this.media, PlaylistLevelType.MAIN);
19996
19609
  }
19997
- get maxBufferLength() {
19998
- const {
19999
- levels,
20000
- level
20001
- } = this;
20002
- const levelInfo = levels == null ? void 0 : levels[level];
20003
- if (!levelInfo) {
20004
- return this.config.maxBufferLength;
20005
- }
20006
- return this.getMaxBufferLength(levelInfo.maxBitrate);
20007
- }
20008
19610
  backtrack(frag) {
20009
19611
  this.couldBacktrack = true;
20010
19612
  // Causes findFragments to backtrack through fragments to find the keyframe
@@ -20110,7 +19712,7 @@ class Hls {
20110
19712
  * Get the video-dev/hls.js package version.
20111
19713
  */
20112
19714
  static get version() {
20113
- return "1.5.7-0.canary.10040";
19715
+ return "1.5.7";
20114
19716
  }
20115
19717
 
20116
19718
  /**
@@ -20173,12 +19775,9 @@ class Hls {
20173
19775
  * The configuration object provided on player instantiation.
20174
19776
  */
20175
19777
  this.userConfig = void 0;
20176
- /**
20177
- * The logger functions used by this player instance, configured on player instantiation.
20178
- */
20179
- this.logger = void 0;
20180
19778
  this.coreComponents = void 0;
20181
19779
  this.networkControllers = void 0;
19780
+ this.started = false;
20182
19781
  this._emitter = new EventEmitter();
20183
19782
  this._autoLevelCapping = -1;
20184
19783
  this._maxHdcpLevel = null;
@@ -20195,11 +19794,11 @@ class Hls {
20195
19794
  this._media = null;
20196
19795
  this.url = null;
20197
19796
  this.triggeringException = void 0;
20198
- const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
20199
- const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
19797
+ enableLogs(userConfig.debug || false, 'Hls instance');
19798
+ const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
20200
19799
  this.userConfig = userConfig;
20201
19800
  if (config.progressive) {
20202
- enableStreamingMode(config, logger);
19801
+ enableStreamingMode(config);
20203
19802
  }
20204
19803
 
20205
19804
  // core controllers and network loaders
@@ -20212,9 +19811,7 @@ class Hls {
20212
19811
  } = config;
20213
19812
  const errorController = new ConfigErrorController(this);
20214
19813
  const abrController = this.abrController = new ConfigAbrController(this);
20215
- // FragmentTracker must be defined before StreamController because the order of event handling is important
20216
- const fragmentTracker = new FragmentTracker(this);
20217
- const bufferController = this.bufferController = new ConfigBufferController(this, fragmentTracker);
19814
+ const bufferController = this.bufferController = new ConfigBufferController(this);
20218
19815
  const capLevelController = this.capLevelController = new ConfigCapLevelController(this);
20219
19816
  const fpsController = new ConfigFpsController(this);
20220
19817
  const playListLoader = new PlaylistLoader(this);
@@ -20223,6 +19820,8 @@ class Hls {
20223
19820
  // ConentSteeringController is defined before LevelController to receive Multivariant Playlist events first
20224
19821
  const contentSteering = ConfigContentSteeringController ? new ConfigContentSteeringController(this) : null;
20225
19822
  const levelController = this.levelController = new LevelController(this, contentSteering);
19823
+ // FragmentTracker must be defined before StreamController because the order of event handling is important
19824
+ const fragmentTracker = new FragmentTracker(this);
20226
19825
  const keyLoader = new KeyLoader(this.config);
20227
19826
  const streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader);
20228
19827
 
@@ -20298,7 +19897,7 @@ class Hls {
20298
19897
  try {
20299
19898
  return this.emit(event, event, eventObject);
20300
19899
  } catch (error) {
20301
- this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
19900
+ logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
20302
19901
  // Prevent recursion in error event handlers that throw #5497
20303
19902
  if (!this.triggeringException) {
20304
19903
  this.triggeringException = true;
@@ -20324,7 +19923,7 @@ class Hls {
20324
19923
  * Dispose of the instance
20325
19924
  */
20326
19925
  destroy() {
20327
- this.logger.log('destroy');
19926
+ logger.log('destroy');
20328
19927
  this.trigger(Events.DESTROYING, undefined);
20329
19928
  this.detachMedia();
20330
19929
  this.removeAllListeners();
@@ -20345,7 +19944,7 @@ class Hls {
20345
19944
  * Attaches Hls.js to a media element
20346
19945
  */
20347
19946
  attachMedia(media) {
20348
- this.logger.log('attachMedia');
19947
+ logger.log('attachMedia');
20349
19948
  this._media = media;
20350
19949
  this.trigger(Events.MEDIA_ATTACHING, {
20351
19950
  media: media
@@ -20356,7 +19955,7 @@ class Hls {
20356
19955
  * Detach Hls.js from the media
20357
19956
  */
20358
19957
  detachMedia() {
20359
- this.logger.log('detachMedia');
19958
+ logger.log('detachMedia');
20360
19959
  this.trigger(Events.MEDIA_DETACHING, undefined);
20361
19960
  this._media = null;
20362
19961
  }
@@ -20373,7 +19972,7 @@ class Hls {
20373
19972
  });
20374
19973
  this._autoLevelCapping = -1;
20375
19974
  this._maxHdcpLevel = null;
20376
- this.logger.log(`loadSource:${loadingSource}`);
19975
+ logger.log(`loadSource:${loadingSource}`);
20377
19976
  if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
20378
19977
  this.detachMedia();
20379
19978
  this.attachMedia(media);
@@ -20392,7 +19991,8 @@ class Hls {
20392
19991
  * Defaults to -1 (None: starts from earliest point)
20393
19992
  */
20394
19993
  startLoad(startPosition = -1) {
20395
- this.logger.log(`startLoad(${startPosition})`);
19994
+ logger.log(`startLoad(${startPosition})`);
19995
+ this.started = true;
20396
19996
  this.networkControllers.forEach(controller => {
20397
19997
  controller.startLoad(startPosition);
20398
19998
  });
@@ -20402,31 +20002,34 @@ class Hls {
20402
20002
  * Stop loading of any stream data.
20403
20003
  */
20404
20004
  stopLoad() {
20405
- this.logger.log('stopLoad');
20005
+ logger.log('stopLoad');
20006
+ this.started = false;
20406
20007
  this.networkControllers.forEach(controller => {
20407
20008
  controller.stopLoad();
20408
20009
  });
20409
20010
  }
20410
20011
 
20411
20012
  /**
20412
- * Resumes stream controller segment loading after `pauseBuffering` has been called.
20013
+ * Resumes stream controller segment loading if previously started.
20413
20014
  */
20414
20015
  resumeBuffering() {
20415
- this.networkControllers.forEach(controller => {
20416
- if (controller.resumeBuffering) {
20417
- controller.resumeBuffering();
20418
- }
20419
- });
20016
+ if (this.started) {
20017
+ this.networkControllers.forEach(controller => {
20018
+ if ('fragmentLoader' in controller) {
20019
+ controller.startLoad(-1);
20020
+ }
20021
+ });
20022
+ }
20420
20023
  }
20421
20024
 
20422
20025
  /**
20423
- * Prevents stream controller from loading new segments until `resumeBuffering` is called.
20026
+ * Stops stream controller segment loading without changing 'started' state like stopLoad().
20424
20027
  * This allows for media buffering to be paused without interupting playlist loading.
20425
20028
  */
20426
20029
  pauseBuffering() {
20427
20030
  this.networkControllers.forEach(controller => {
20428
- if (controller.pauseBuffering) {
20429
- controller.pauseBuffering();
20031
+ if ('fragmentLoader' in controller) {
20032
+ controller.stopLoad();
20430
20033
  }
20431
20034
  });
20432
20035
  }
@@ -20435,7 +20038,7 @@ class Hls {
20435
20038
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
20436
20039
  */
20437
20040
  swapAudioCodec() {
20438
- this.logger.log('swapAudioCodec');
20041
+ logger.log('swapAudioCodec');
20439
20042
  this.streamController.swapAudioCodec();
20440
20043
  }
20441
20044
 
@@ -20446,7 +20049,7 @@ class Hls {
20446
20049
  * Automatic recovery of media-errors by this process is configurable.
20447
20050
  */
20448
20051
  recoverMediaError() {
20449
- this.logger.log('recoverMediaError');
20052
+ logger.log('recoverMediaError');
20450
20053
  const media = this._media;
20451
20054
  this.detachMedia();
20452
20055
  if (media) {
@@ -20476,7 +20079,7 @@ class Hls {
20476
20079
  * Set quality level index immediately. This will flush the current buffer to replace the quality asap. That means playback will interrupt at least shortly to re-buffer and re-sync eventually. Set to -1 for automatic level selection.
20477
20080
  */
20478
20081
  set currentLevel(newLevel) {
20479
- this.logger.log(`set currentLevel:${newLevel}`);
20082
+ logger.log(`set currentLevel:${newLevel}`);
20480
20083
  this.levelController.manualLevel = newLevel;
20481
20084
  this.streamController.immediateLevelSwitch();
20482
20085
  }
@@ -20495,7 +20098,7 @@ class Hls {
20495
20098
  * @param newLevel - Pass -1 for automatic level selection
20496
20099
  */
20497
20100
  set nextLevel(newLevel) {
20498
- this.logger.log(`set nextLevel:${newLevel}`);
20101
+ logger.log(`set nextLevel:${newLevel}`);
20499
20102
  this.levelController.manualLevel = newLevel;
20500
20103
  this.streamController.nextLevelSwitch();
20501
20104
  }
@@ -20514,7 +20117,7 @@ class Hls {
20514
20117
  * @param newLevel - Pass -1 for automatic level selection
20515
20118
  */
20516
20119
  set loadLevel(newLevel) {
20517
- this.logger.log(`set loadLevel:${newLevel}`);
20120
+ logger.log(`set loadLevel:${newLevel}`);
20518
20121
  this.levelController.manualLevel = newLevel;
20519
20122
  }
20520
20123
 
@@ -20545,7 +20148,7 @@ class Hls {
20545
20148
  * Sets "first-level", see getter.
20546
20149
  */
20547
20150
  set firstLevel(newLevel) {
20548
- this.logger.log(`set firstLevel:${newLevel}`);
20151
+ logger.log(`set firstLevel:${newLevel}`);
20549
20152
  this.levelController.firstLevel = newLevel;
20550
20153
  }
20551
20154
 
@@ -20570,7 +20173,7 @@ class Hls {
20570
20173
  * (determined from download of first segment)
20571
20174
  */
20572
20175
  set startLevel(newLevel) {
20573
- this.logger.log(`set startLevel:${newLevel}`);
20176
+ logger.log(`set startLevel:${newLevel}`);
20574
20177
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
20575
20178
  if (newLevel !== -1) {
20576
20179
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -20645,7 +20248,7 @@ class Hls {
20645
20248
  */
20646
20249
  set autoLevelCapping(newLevel) {
20647
20250
  if (this._autoLevelCapping !== newLevel) {
20648
- this.logger.log(`set autoLevelCapping:${newLevel}`);
20251
+ logger.log(`set autoLevelCapping:${newLevel}`);
20649
20252
  this._autoLevelCapping = newLevel;
20650
20253
  this.levelController.checkMaxAutoUpdated();
20651
20254
  }
@@ -20750,9 +20353,6 @@ class Hls {
20750
20353
  get mainForwardBufferInfo() {
20751
20354
  return this.streamController.getMainFwdBufferInfo();
20752
20355
  }
20753
- get maxBufferLength() {
20754
- return this.streamController.maxBufferLength;
20755
- }
20756
20356
 
20757
20357
  /**
20758
20358
  * Find and select the best matching audio track, making a level switch when a Group change is necessary.
@@ -20927,5 +20527,5 @@ var KeySystemFormats = empty.KeySystemFormats;
20927
20527
  var KeySystems = empty.KeySystems;
20928
20528
  var SubtitleStreamController = empty.SubtitleStreamController;
20929
20529
  var TimelineController = empty.TimelineController;
20930
- export { AbrController, AttrList, HevcVideoParser as AudioStreamController, HevcVideoParser as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, HevcVideoParser as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, HevcVideoParser as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, HevcVideoParser as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported };
20530
+ export { AbrController, AttrList, Cues as AudioStreamController, Cues as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, Cues as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, Cues as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, Cues as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported };
20931
20531
  //# sourceMappingURL=hls.light.mjs.map