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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +0 -1
  2. package/dist/hls-demo.js +0 -10
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +1169 -2069
  5. package/dist/hls.js.d.ts +51 -65
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +875 -1158
  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 +709 -993
  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 +869 -1756
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +20 -20
  20. package/src/config.ts +2 -3
  21. package/src/controller/abr-controller.ts +40 -31
  22. package/src/controller/audio-stream-controller.ts +16 -15
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +8 -20
  25. package/src/controller/base-stream-controller.ts +33 -149
  26. package/src/controller/buffer-controller.ts +11 -11
  27. package/src/controller/cap-level-controller.ts +2 -1
  28. package/src/controller/cmcd-controller.ts +6 -27
  29. package/src/controller/content-steering-controller.ts +6 -8
  30. package/src/controller/eme-controller.ts +22 -9
  31. package/src/controller/error-controller.ts +8 -6
  32. package/src/controller/fps-controller.ts +3 -2
  33. package/src/controller/gap-controller.ts +16 -43
  34. package/src/controller/latency-controller.ts +11 -9
  35. package/src/controller/level-controller.ts +18 -12
  36. package/src/controller/stream-controller.ts +34 -27
  37. package/src/controller/subtitle-stream-controller.ts +14 -13
  38. package/src/controller/subtitle-track-controller.ts +3 -5
  39. package/src/controller/timeline-controller.ts +30 -23
  40. package/src/crypt/aes-crypto.ts +2 -21
  41. package/src/crypt/decrypter.ts +18 -32
  42. package/src/crypt/fast-aes-key.ts +5 -24
  43. package/src/demux/audio/adts.ts +4 -9
  44. package/src/demux/sample-aes.ts +0 -2
  45. package/src/demux/transmuxer-interface.ts +12 -4
  46. package/src/demux/transmuxer-worker.ts +4 -4
  47. package/src/demux/transmuxer.ts +3 -16
  48. package/src/demux/tsdemuxer.ts +37 -71
  49. package/src/demux/video/avc-video-parser.ts +119 -208
  50. package/src/demux/video/base-video-parser.ts +2 -134
  51. package/src/demux/video/exp-golomb.ts +208 -0
  52. package/src/events.ts +0 -7
  53. package/src/hls.ts +34 -42
  54. package/src/loader/fragment-loader.ts +2 -9
  55. package/src/loader/key-loader.ts +0 -2
  56. package/src/loader/level-key.ts +9 -10
  57. package/src/loader/playlist-loader.ts +5 -4
  58. package/src/remux/mp4-generator.ts +1 -196
  59. package/src/remux/mp4-remuxer.ts +7 -23
  60. package/src/task-loop.ts +2 -5
  61. package/src/types/component-api.ts +0 -2
  62. package/src/types/demuxer.ts +0 -3
  63. package/src/types/events.ts +0 -4
  64. package/src/utils/codecs.ts +4 -33
  65. package/src/utils/logger.ts +24 -54
  66. package/src/utils/mp4-tools.ts +6 -4
  67. package/src/crypt/decrypter-aes-mode.ts +0 -4
  68. package/src/demux/video/hevc-video-parser.ts +0 -746
  69. 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.6"}`);
415
+ } catch (e) {
416
+ exportedLogger = fakeLogger;
417
+ }
418
+ } else {
419
+ exportedLogger = fakeLogger;
420
+ }
421
+ }
422
+ const logger = exportedLogger;
423
+
373
424
  const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
374
425
  const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
375
426
 
@@ -451,79 +502,6 @@ class AttrList {
451
502
  }
452
503
  }
453
504
 
454
- class Logger {
455
- constructor(label, logger) {
456
- this.trace = void 0;
457
- this.debug = void 0;
458
- this.log = void 0;
459
- this.warn = void 0;
460
- this.info = void 0;
461
- this.error = void 0;
462
- const lb = `[${label}]:`;
463
- this.trace = noop;
464
- this.debug = logger.debug.bind(null, lb);
465
- this.log = logger.log.bind(null, lb);
466
- this.warn = logger.warn.bind(null, lb);
467
- this.info = logger.info.bind(null, lb);
468
- this.error = logger.error.bind(null, lb);
469
- }
470
- }
471
- const noop = function noop() {};
472
- const fakeLogger = {
473
- trace: noop,
474
- debug: noop,
475
- log: noop,
476
- warn: noop,
477
- info: noop,
478
- error: noop
479
- };
480
- function createLogger() {
481
- return _extends({}, fakeLogger);
482
- }
483
-
484
- // let lastCallTime;
485
- // function formatMsgWithTimeInfo(type, msg) {
486
- // const now = Date.now();
487
- // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
488
- // lastCallTime = now;
489
- // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
490
- // return msg;
491
- // }
492
-
493
- function consolePrintFn(type, id) {
494
- const func = self.console[type];
495
- return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
496
- }
497
- function getLoggerFn(key, debugConfig, id) {
498
- return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
499
- }
500
- const exportedLogger = createLogger();
501
- function enableLogs(debugConfig, context, id) {
502
- // check that console is available
503
- const newLogger = createLogger();
504
- if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
505
- const keys = [
506
- // Remove out from list here to hard-disable a log-level
507
- // 'trace',
508
- 'debug', 'log', 'info', 'warn', 'error'];
509
- keys.forEach(key => {
510
- newLogger[key] = getLoggerFn(key, debugConfig, id);
511
- });
512
- // Some browsers don't allow to use bind on console object anyway
513
- // fallback to default if needed
514
- try {
515
- newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.6-0.canary.9999"}`);
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.
@@ -1505,11 +1463,13 @@ function parseSegmentIndex(sidx) {
1505
1463
  let earliestPresentationTime = 0;
1506
1464
  let firstOffset = 0;
1507
1465
  if (version === 0) {
1508
- earliestPresentationTime = readUint32(sidx, index += 4);
1509
- firstOffset = readUint32(sidx, index += 4);
1466
+ earliestPresentationTime = readUint32(sidx, index);
1467
+ firstOffset = readUint32(sidx, index + 4);
1468
+ index += 8;
1510
1469
  } else {
1511
- earliestPresentationTime = readUint64(sidx, index += 8);
1512
- firstOffset = readUint64(sidx, index += 8);
1470
+ earliestPresentationTime = readUint64(sidx, index);
1471
+ firstOffset = readUint64(sidx, index + 8);
1472
+ index += 16;
1513
1473
  }
1514
1474
 
1515
1475
  // skip reserved
@@ -2473,12 +2433,12 @@ class LevelKey {
2473
2433
  this.keyFormatVersions = formatversions;
2474
2434
  this.iv = iv;
2475
2435
  this.encrypted = method ? method !== 'NONE' : false;
2476
- this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
2436
+ this.isCommonEncryption = this.encrypted && method !== 'AES-128';
2477
2437
  }
2478
2438
  isSupported() {
2479
2439
  // If it's Segment encryption or No encryption, just select that key system
2480
2440
  if (this.method) {
2481
- if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
2441
+ if (this.method === 'AES-128' || this.method === 'NONE') {
2482
2442
  return true;
2483
2443
  }
2484
2444
  if (this.keyFormat === 'identity') {
@@ -2492,13 +2452,14 @@ class LevelKey {
2492
2452
  if (!this.encrypted || !this.uri) {
2493
2453
  return null;
2494
2454
  }
2495
- if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
2455
+ if (this.method === 'AES-128' && this.uri && !this.iv) {
2496
2456
  if (typeof sn !== 'number') {
2497
2457
  // We are fetching decryption data for a initialization segment
2498
- // If the segment was encrypted with AES-128/256
2458
+ // If the segment was encrypted with AES-128
2499
2459
  // It must have an IV defined. We cannot substitute the Segment Number in.
2500
- logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2501
-
2460
+ if (this.method === 'AES-128' && !this.iv) {
2461
+ logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
2462
+ }
2502
2463
  // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
2503
2464
  sn = 0;
2504
2465
  }
@@ -2645,28 +2606,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
2645
2606
  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
2646
2607
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
2647
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
2648
2613
  const codecsToCheck = {
2649
- // Idealy fLaC and Opus would be first (spec-compliant) but
2650
- // some browsers will report that fLaC is supported then fail.
2651
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
2652
2614
  flac: ['flac', 'fLaC', 'FLAC'],
2653
- opus: ['opus', 'Opus'],
2654
- // Replace audio codec info if browser does not support mp4a.40.34,
2655
- // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
2656
- 'mp4a.40.34': ['mp3']
2615
+ opus: ['opus', 'Opus']
2657
2616
  }[lowerCaseCodec];
2658
2617
  for (let i = 0; i < codecsToCheck.length; i++) {
2659
- var _getMediaSource;
2660
2618
  if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
2661
2619
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
2662
2620
  return codecsToCheck[i];
2663
- } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
2664
- return '';
2665
2621
  }
2666
2622
  }
2667
2623
  return lowerCaseCodec;
2668
2624
  }
2669
- const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
2625
+ const AUDIO_CODEC_REGEXP = /flac|opus/i;
2670
2626
  function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
2671
2627
  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
2672
2628
  }
@@ -2689,16 +2645,6 @@ function convertAVC1ToAVCOTI(codec) {
2689
2645
  }
2690
2646
  return codec;
2691
2647
  }
2692
- function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
2693
- const MediaSource = getMediaSource(preferManagedMediaSource) || {
2694
- isTypeSupported: () => false
2695
- };
2696
- return {
2697
- mpeg: MediaSource.isTypeSupported('audio/mpeg'),
2698
- mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
2699
- ac3: false
2700
- };
2701
- }
2702
2648
 
2703
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;
2704
2650
  const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -3499,10 +3445,10 @@ class PlaylistLoader {
3499
3445
  const loaderContext = loader.context;
3500
3446
  if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
3501
3447
  // same URL can't overlap
3502
- this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
3448
+ logger.trace('[playlist-loader]: playlist request ongoing');
3503
3449
  return;
3504
3450
  }
3505
- 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}`);
3506
3452
  loader.abort();
3507
3453
  }
3508
3454
 
@@ -3612,7 +3558,7 @@ class PlaylistLoader {
3612
3558
  // alt audio rendition in which quality levels (main)
3613
3559
  // contains both audio+video. but with mixed audio track not signaled
3614
3560
  if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
3615
- 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');
3616
3562
  audioTracks.unshift({
3617
3563
  type: 'main',
3618
3564
  name: 'main',
@@ -3711,7 +3657,7 @@ class PlaylistLoader {
3711
3657
  message += ` id: ${context.id} group-id: "${context.groupId}"`;
3712
3658
  }
3713
3659
  const error = new Error(message);
3714
- this.hls.logger.warn(`[playlist-loader]: ${message}`);
3660
+ logger.warn(`[playlist-loader]: ${message}`);
3715
3661
  let details = ErrorDetails.UNKNOWN;
3716
3662
  let fatal = false;
3717
3663
  const loader = this.getInternalLoader(context);
@@ -4276,47 +4222,7 @@ class LatencyController {
4276
4222
  this.currentTime = 0;
4277
4223
  this.stallCount = 0;
4278
4224
  this._latency = null;
4279
- this.onTimeupdate = () => {
4280
- const {
4281
- media,
4282
- levelDetails
4283
- } = this;
4284
- if (!media || !levelDetails) {
4285
- return;
4286
- }
4287
- this.currentTime = media.currentTime;
4288
- const latency = this.computeLatency();
4289
- if (latency === null) {
4290
- return;
4291
- }
4292
- this._latency = latency;
4293
-
4294
- // Adapt playbackRate to meet target latency in low-latency mode
4295
- const {
4296
- lowLatencyMode,
4297
- maxLiveSyncPlaybackRate
4298
- } = this.config;
4299
- if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
4300
- return;
4301
- }
4302
- const targetLatency = this.targetLatency;
4303
- if (targetLatency === null) {
4304
- return;
4305
- }
4306
- const distanceFromTarget = latency - targetLatency;
4307
- // Only adjust playbackRate when within one target duration of targetLatency
4308
- // and more than one second from under-buffering.
4309
- // Playback further than one target duration from target can be considered DVR playback.
4310
- const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
4311
- const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
4312
- if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
4313
- const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
4314
- const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
4315
- media.playbackRate = Math.min(max, Math.max(1, rate));
4316
- } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
4317
- media.playbackRate = 1;
4318
- }
4319
- };
4225
+ this.timeupdateHandler = () => this.timeupdate();
4320
4226
  this.hls = hls;
4321
4227
  this.config = hls.config;
4322
4228
  this.registerListeners();
@@ -4408,7 +4314,7 @@ class LatencyController {
4408
4314
  this.onMediaDetaching();
4409
4315
  this.levelDetails = null;
4410
4316
  // @ts-ignore
4411
- this.hls = null;
4317
+ this.hls = this.timeupdateHandler = null;
4412
4318
  }
4413
4319
  registerListeners() {
4414
4320
  this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
@@ -4426,11 +4332,11 @@ class LatencyController {
4426
4332
  }
4427
4333
  onMediaAttached(event, data) {
4428
4334
  this.media = data.media;
4429
- this.media.addEventListener('timeupdate', this.onTimeupdate);
4335
+ this.media.addEventListener('timeupdate', this.timeupdateHandler);
4430
4336
  }
4431
4337
  onMediaDetaching() {
4432
4338
  if (this.media) {
4433
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4339
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4434
4340
  this.media = null;
4435
4341
  }
4436
4342
  }
@@ -4444,10 +4350,10 @@ class LatencyController {
4444
4350
  }) {
4445
4351
  this.levelDetails = details;
4446
4352
  if (details.advanced) {
4447
- this.onTimeupdate();
4353
+ this.timeupdate();
4448
4354
  }
4449
4355
  if (!details.live && this.media) {
4450
- this.media.removeEventListener('timeupdate', this.onTimeupdate);
4356
+ this.media.removeEventListener('timeupdate', this.timeupdateHandler);
4451
4357
  }
4452
4358
  }
4453
4359
  onError(event, data) {
@@ -4457,7 +4363,48 @@ class LatencyController {
4457
4363
  }
4458
4364
  this.stallCount++;
4459
4365
  if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
4460
- 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;
4461
4408
  }
4462
4409
  }
4463
4410
  estimateLiveEdge() {
@@ -5229,13 +5176,18 @@ var ErrorActionFlags = {
5229
5176
  MoveAllAlternatesMatchingHDCP: 2,
5230
5177
  SwitchToSDR: 4
5231
5178
  }; // Reserved for future use
5232
- class ErrorController extends Logger {
5179
+ class ErrorController {
5233
5180
  constructor(hls) {
5234
- super('error-controller', hls.logger);
5235
5181
  this.hls = void 0;
5236
5182
  this.playlistError = 0;
5237
5183
  this.penalizedRenditions = {};
5184
+ this.log = void 0;
5185
+ this.warn = void 0;
5186
+ this.error = void 0;
5238
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]:`);
5239
5191
  this.registerListeners();
5240
5192
  }
5241
5193
  registerListeners() {
@@ -5587,13 +5539,16 @@ class ErrorController extends Logger {
5587
5539
  }
5588
5540
  }
5589
5541
 
5590
- class BasePlaylistController extends Logger {
5542
+ class BasePlaylistController {
5591
5543
  constructor(hls, logPrefix) {
5592
- super(logPrefix, hls.logger);
5593
5544
  this.hls = void 0;
5594
5545
  this.timer = -1;
5595
5546
  this.requestScheduled = -1;
5596
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}:`);
5597
5552
  this.hls = hls;
5598
5553
  }
5599
5554
  destroy() {
@@ -5626,7 +5581,7 @@ class BasePlaylistController extends Logger {
5626
5581
  try {
5627
5582
  uri = new self.URL(attr.URI, previous.url).href;
5628
5583
  } catch (error) {
5629
- this.warn(`Could not construct new URL for Rendition Report: ${error}`);
5584
+ logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
5630
5585
  uri = attr.URI || '';
5631
5586
  }
5632
5587
  // Use exact match. Otherwise, the last partial match, if any, will be used
@@ -5713,12 +5668,7 @@ class BasePlaylistController extends Logger {
5713
5668
  const cdnAge = lastAdvanced + details.ageHeader;
5714
5669
  let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
5715
5670
  if (currentGoal > 0) {
5716
- if (cdnAge > details.targetduration * 3) {
5717
- // Omit segment and part directives when the last response was more than 3 target durations ago,
5718
- this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
5719
- msn = undefined;
5720
- part = undefined;
5721
- } else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
5671
+ if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
5722
5672
  // If we attempted to get the next or latest playlist update, but currentGoal increased,
5723
5673
  // then we either can't catchup, or the "age" header cannot be trusted.
5724
5674
  this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
@@ -6177,9 +6127,8 @@ function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
6177
6127
  }, {});
6178
6128
  }
6179
6129
 
6180
- class AbrController extends Logger {
6130
+ class AbrController {
6181
6131
  constructor(_hls) {
6182
- super('abr', _hls.logger);
6183
6132
  this.hls = void 0;
6184
6133
  this.lastLevelLoadSec = 0;
6185
6134
  this.lastLoadedFragLevel = -1;
@@ -6293,7 +6242,7 @@ class AbrController extends Logger {
6293
6242
  this.resetEstimator(nextLoadLevelBitrate);
6294
6243
  }
6295
6244
  this.clearTimer();
6296
- 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;
6297
6246
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
6298
6247
  Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
6299
6248
  Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
@@ -6313,7 +6262,7 @@ class AbrController extends Logger {
6313
6262
  }
6314
6263
  resetEstimator(abrEwmaDefaultEstimate) {
6315
6264
  if (abrEwmaDefaultEstimate) {
6316
- this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6265
+ logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
6317
6266
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
6318
6267
  }
6319
6268
  this.firstSelection = -1;
@@ -6545,7 +6494,7 @@ class AbrController extends Logger {
6545
6494
  }
6546
6495
  const firstLevel = this.hls.firstLevel;
6547
6496
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
6548
- 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}`);
6549
6498
  return clamped;
6550
6499
  }
6551
6500
  get forcedAutoLevel() {
@@ -6583,8 +6532,7 @@ class AbrController extends Logger {
6583
6532
  return nextABRAutoLevel;
6584
6533
  }
6585
6534
  getAutoLevelKey() {
6586
- var _this$hls$mainForward;
6587
- return `${this.getBwEstimate()}_${(_this$hls$mainForward = this.hls.mainForwardBufferInfo) == null ? void 0 : _this$hls$mainForward.len}`;
6535
+ return `${this.getBwEstimate()}_${this.getStarvationDelay().toFixed(2)}`;
6588
6536
  }
6589
6537
  getNextABRAutoLevel() {
6590
6538
  const {
@@ -6595,18 +6543,12 @@ class AbrController extends Logger {
6595
6543
  const {
6596
6544
  maxAutoLevel,
6597
6545
  config,
6598
- minAutoLevel,
6599
- media
6546
+ minAutoLevel
6600
6547
  } = hls;
6601
6548
  const currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0;
6602
-
6603
- // playbackRate is the absolute value of the playback rate; if media.playbackRate is 0, we use 1 to load as
6604
- // if we're playing back at the normal rate.
6605
- const playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0;
6606
6549
  const avgbw = this.getBwEstimate();
6607
6550
  // bufferStarvationDelay is the wall-clock time left until the playback buffer is exhausted.
6608
- const bufferInfo = hls.mainForwardBufferInfo;
6609
- const bufferStarvationDelay = (bufferInfo ? bufferInfo.len : 0) / playbackRate;
6551
+ const bufferStarvationDelay = this.getStarvationDelay();
6610
6552
  let bwFactor = config.abrBandWidthFactor;
6611
6553
  let bwUpFactor = config.abrBandWidthUpFactor;
6612
6554
 
@@ -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
  }
@@ -6649,6 +6591,18 @@ class AbrController extends Logger {
6649
6591
  // or if bitrate is not lower, continue to use loadLevel
6650
6592
  return hls.loadLevel;
6651
6593
  }
6594
+ getStarvationDelay() {
6595
+ const hls = this.hls;
6596
+ const media = hls.media;
6597
+ if (!media) {
6598
+ return Infinity;
6599
+ }
6600
+ // playbackRate is the absolute value of the playback rate; if media.playbackRate is 0, we use 1 to load as
6601
+ // if we're playing back at the normal rate.
6602
+ const playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0;
6603
+ const bufferInfo = hls.mainForwardBufferInfo;
6604
+ return (bufferInfo ? bufferInfo.len : 0) / playbackRate;
6605
+ }
6652
6606
  getBwEstimate() {
6653
6607
  return this.bwEstimator.canEstimate() ? this.bwEstimator.getEstimate() : this.hls.config.abrEwmaDefaultEstimate;
6654
6608
  }
@@ -6698,7 +6652,7 @@ class AbrController extends Logger {
6698
6652
  currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
6699
6653
  currentFrameRate = minFramerate;
6700
6654
  currentBw = Math.max(currentBw, minBitrate);
6701
- this.log(`picked start tier ${JSON.stringify(startTier)}`);
6655
+ logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
6702
6656
  } else {
6703
6657
  currentCodecSet = level == null ? void 0 : level.codecSet;
6704
6658
  currentVideoRange = level == null ? void 0 : level.videoRange;
@@ -6751,9 +6705,9 @@ class AbrController extends Logger {
6751
6705
  const forcedAutoLevel = this.forcedAutoLevel;
6752
6706
  if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
6753
6707
  if (levelsSkipped.length) {
6754
- 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}`);
6755
6709
  }
6756
- 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}`);
6757
6711
  }
6758
6712
  if (firstSelection) {
6759
6713
  this.firstSelection = i;
@@ -6989,9 +6943,8 @@ class BufferOperationQueue {
6989
6943
  }
6990
6944
 
6991
6945
  const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
6992
- class BufferController extends Logger {
6946
+ class BufferController {
6993
6947
  constructor(hls) {
6994
- super('buffer-controller', hls.logger);
6995
6948
  // The level details used to determine duration, target-duration and live
6996
6949
  this.details = null;
6997
6950
  // cache the self generated object url to detect hijack of video tag
@@ -7021,6 +6974,9 @@ class BufferController extends Logger {
7021
6974
  this.tracks = {};
7022
6975
  this.pendingTracks = {};
7023
6976
  this.sourceBuffer = void 0;
6977
+ this.log = void 0;
6978
+ this.warn = void 0;
6979
+ this.error = void 0;
7024
6980
  this._onEndStreaming = event => {
7025
6981
  if (!this.hls) {
7026
6982
  return;
@@ -7066,11 +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;
7029
+ const logPrefix = '[buffer-controller]';
7073
7030
  this.appendSource = hls.config.preferManagedMediaSource;
7031
+ this.log = logger.log.bind(logger, logPrefix);
7032
+ this.warn = logger.warn.bind(logger, logPrefix);
7033
+ this.error = logger.error.bind(logger, logPrefix);
7074
7034
  this._initSourceBuffer();
7075
7035
  this.registerListeners();
7076
7036
  }
@@ -7083,12 +7043,6 @@ class BufferController extends Logger {
7083
7043
  this.lastMpegAudioChunk = null;
7084
7044
  // @ts-ignore
7085
7045
  this.hls = null;
7086
- // @ts-ignore
7087
- this._onMediaSourceOpen = this._onMediaSourceClose = null;
7088
- // @ts-ignore
7089
- this._onMediaSourceEnded = null;
7090
- // @ts-ignore
7091
- this._onStartStreaming = this._onEndStreaming = null;
7092
7046
  }
7093
7047
  registerListeners() {
7094
7048
  const {
@@ -7251,7 +7205,6 @@ class BufferController extends Logger {
7251
7205
  this.resetBuffer(type);
7252
7206
  });
7253
7207
  this._initSourceBuffer();
7254
- this.hls.resumeBuffering();
7255
7208
  }
7256
7209
  resetBuffer(type) {
7257
7210
  const sb = this.sourceBuffer[type];
@@ -8089,7 +8042,7 @@ class CapLevelController {
8089
8042
  const hls = this.hls;
8090
8043
  const maxLevel = this.getMaxLevel(levels.length - 1);
8091
8044
  if (maxLevel !== this.autoLevelCapping) {
8092
- hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
8045
+ logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
8093
8046
  }
8094
8047
  hls.autoLevelCapping = maxLevel;
8095
8048
  if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
@@ -8267,10 +8220,10 @@ class FPSController {
8267
8220
  totalDroppedFrames: droppedFrames
8268
8221
  });
8269
8222
  if (droppedFPS > 0) {
8270
- // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
8223
+ // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
8271
8224
  if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
8272
8225
  let currentLevel = hls.currentLevel;
8273
- hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
8226
+ logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
8274
8227
  if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
8275
8228
  currentLevel = currentLevel - 1;
8276
8229
  hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
@@ -8303,10 +8256,10 @@ class FPSController {
8303
8256
  }
8304
8257
 
8305
8258
  const PATHWAY_PENALTY_DURATION_MS = 300000;
8306
- class ContentSteeringController extends Logger {
8259
+ class ContentSteeringController {
8307
8260
  constructor(hls) {
8308
- super('content-steering', hls.logger);
8309
8261
  this.hls = void 0;
8262
+ this.log = void 0;
8310
8263
  this.loader = null;
8311
8264
  this.uri = null;
8312
8265
  this.pathwayId = '.';
@@ -8321,6 +8274,7 @@ class ContentSteeringController extends Logger {
8321
8274
  this.subtitleTracks = null;
8322
8275
  this.penalizedPathways = {};
8323
8276
  this.hls = hls;
8277
+ this.log = logger.log.bind(logger, `[content-steering]:`);
8324
8278
  this.registerListeners();
8325
8279
  }
8326
8280
  registerListeners() {
@@ -8444,7 +8398,7 @@ class ContentSteeringController extends Logger {
8444
8398
  errorAction.resolved = this.pathwayId !== errorPathway;
8445
8399
  }
8446
8400
  if (!errorAction.resolved) {
8447
- 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)}`);
8401
+ 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)}`);
8448
8402
  }
8449
8403
  }
8450
8404
  }
@@ -8615,7 +8569,7 @@ class ContentSteeringController extends Logger {
8615
8569
  onSuccess: (response, stats, context, networkDetails) => {
8616
8570
  this.log(`Loaded steering manifest: "${url}"`);
8617
8571
  const steeringData = response.data;
8618
- if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
8572
+ if (steeringData.VERSION !== 1) {
8619
8573
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
8620
8574
  return;
8621
8575
  }
@@ -9523,7 +9477,7 @@ const hlsDefaultConfig = _objectSpread2(_objectSpread2({
9523
9477
  });
9524
9478
  function timelineConfig() {
9525
9479
  return {
9526
- cueHandler: HevcVideoParser,
9480
+ cueHandler: Cues,
9527
9481
  // used by timeline-controller
9528
9482
  enableWebVTT: false,
9529
9483
  // used by timeline-controller
@@ -9554,7 +9508,7 @@ function timelineConfig() {
9554
9508
  /**
9555
9509
  * @ignore
9556
9510
  */
9557
- function mergeConfig(defaultConfig, userConfig, logger) {
9511
+ function mergeConfig(defaultConfig, userConfig) {
9558
9512
  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
9559
9513
  throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
9560
9514
  }
@@ -9624,7 +9578,7 @@ function deepCpy(obj) {
9624
9578
  /**
9625
9579
  * @ignore
9626
9580
  */
9627
- function enableStreamingMode(config, logger) {
9581
+ function enableStreamingMode(config) {
9628
9582
  const currentLoader = config.loader;
9629
9583
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
9630
9584
  // If a developer has configured their own loader, respect that choice
@@ -9641,9 +9595,10 @@ function enableStreamingMode(config, logger) {
9641
9595
  }
9642
9596
  }
9643
9597
 
9598
+ let chromeOrFirefox;
9644
9599
  class LevelController extends BasePlaylistController {
9645
9600
  constructor(hls, contentSteeringController) {
9646
- super(hls, 'level-controller');
9601
+ super(hls, '[level-controller]');
9647
9602
  this._levels = [];
9648
9603
  this._firstLevel = -1;
9649
9604
  this._maxAutoLevel = -1;
@@ -9714,15 +9669,23 @@ class LevelController extends BasePlaylistController {
9714
9669
  let videoCodecFound = false;
9715
9670
  let audioCodecFound = false;
9716
9671
  data.levels.forEach(levelParsed => {
9717
- var _videoCodec;
9672
+ var _audioCodec, _videoCodec;
9718
9673
  const attributes = levelParsed.attrs;
9674
+
9675
+ // erase audio codec info if browser does not support mp4a.40.34.
9676
+ // demuxer will autodetect codec and fallback to mpeg/audio
9719
9677
  let {
9720
9678
  audioCodec,
9721
9679
  videoCodec
9722
9680
  } = levelParsed;
9681
+ if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
9682
+ chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
9683
+ if (chromeOrFirefox) {
9684
+ levelParsed.audioCodec = audioCodec = undefined;
9685
+ }
9686
+ }
9723
9687
  if (audioCodec) {
9724
- // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
9725
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
9688
+ levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
9726
9689
  }
9727
9690
  if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
9728
9691
  videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
@@ -10064,12 +10027,7 @@ class LevelController extends BasePlaylistController {
10064
10027
  if (curLevel.fragmentError === 0) {
10065
10028
  curLevel.loadError = 0;
10066
10029
  }
10067
- // Ignore matching details populated by loading a Media Playlist directly
10068
- let previousDetails = curLevel.details;
10069
- if (previousDetails === data.details && previousDetails.advanced) {
10070
- previousDetails = undefined;
10071
- }
10072
- this.playlistLoaded(level, data, previousDetails);
10030
+ this.playlistLoaded(level, data, curLevel.details);
10073
10031
  } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
10074
10032
  // received a delta playlist update that cannot be merged
10075
10033
  details.deltaUpdateFailed = true;
@@ -10855,8 +10813,8 @@ function createLoaderContext(frag, part = null) {
10855
10813
  var _frag$decryptdata;
10856
10814
  let byteRangeStart = start;
10857
10815
  let byteRangeEnd = end;
10858
- if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
10859
- // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
10816
+ if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
10817
+ // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
10860
10818
  // has the unencrypted size specified in the range.
10861
10819
  // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
10862
10820
  const fragmentLen = end - start;
@@ -10889,9 +10847,6 @@ function createGapLoadError(frag, part) {
10889
10847
  (part ? part : frag).stats.aborted = true;
10890
10848
  return new LoadError(errorData);
10891
10849
  }
10892
- function isMethodFullSegmentAesCbc(method) {
10893
- return method === 'AES-128' || method === 'AES-256';
10894
- }
10895
10850
  class LoadError extends Error {
10896
10851
  constructor(data) {
10897
10852
  super(data.error.message);
@@ -11037,8 +10992,6 @@ class KeyLoader {
11037
10992
  }
11038
10993
  return this.loadKeyEME(keyInfo, frag);
11039
10994
  case 'AES-128':
11040
- case 'AES-256':
11041
- case 'AES-256-CTR':
11042
10995
  return this.loadKeyHTTP(keyInfo, frag);
11043
10996
  default:
11044
10997
  return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
@@ -11174,9 +11127,8 @@ class KeyLoader {
11174
11127
  * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
11175
11128
  * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
11176
11129
  */
11177
- class TaskLoop extends Logger {
11178
- constructor(label, logger) {
11179
- super(label, logger);
11130
+ class TaskLoop {
11131
+ constructor() {
11180
11132
  this._boundTick = void 0;
11181
11133
  this._tickTimer = null;
11182
11134
  this._tickInterval = null;
@@ -11444,61 +11396,33 @@ function alignMediaPlaylistByPDT(details, refDetails) {
11444
11396
  }
11445
11397
 
11446
11398
  class AESCrypto {
11447
- constructor(subtle, iv, aesMode) {
11399
+ constructor(subtle, iv) {
11448
11400
  this.subtle = void 0;
11449
11401
  this.aesIV = void 0;
11450
- this.aesMode = void 0;
11451
11402
  this.subtle = subtle;
11452
11403
  this.aesIV = iv;
11453
- this.aesMode = aesMode;
11454
11404
  }
11455
11405
  decrypt(data, key) {
11456
- switch (this.aesMode) {
11457
- case DecrypterAesMode.cbc:
11458
- return this.subtle.decrypt({
11459
- name: 'AES-CBC',
11460
- iv: this.aesIV
11461
- }, key, data);
11462
- case DecrypterAesMode.ctr:
11463
- return this.subtle.decrypt({
11464
- name: 'AES-CTR',
11465
- counter: this.aesIV,
11466
- length: 64
11467
- },
11468
- //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
11469
- key, data);
11470
- default:
11471
- throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
11472
- }
11406
+ return this.subtle.decrypt({
11407
+ name: 'AES-CBC',
11408
+ iv: this.aesIV
11409
+ }, key, data);
11473
11410
  }
11474
11411
  }
11475
11412
 
11476
11413
  class FastAESKey {
11477
- constructor(subtle, key, aesMode) {
11414
+ constructor(subtle, key) {
11478
11415
  this.subtle = void 0;
11479
11416
  this.key = void 0;
11480
- this.aesMode = void 0;
11481
11417
  this.subtle = subtle;
11482
11418
  this.key = key;
11483
- this.aesMode = aesMode;
11484
11419
  }
11485
11420
  expandKey() {
11486
- const subtleAlgoName = getSubtleAlgoName(this.aesMode);
11487
11421
  return this.subtle.importKey('raw', this.key, {
11488
- name: subtleAlgoName
11422
+ name: 'AES-CBC'
11489
11423
  }, false, ['encrypt', 'decrypt']);
11490
11424
  }
11491
11425
  }
11492
- function getSubtleAlgoName(aesMode) {
11493
- switch (aesMode) {
11494
- case DecrypterAesMode.cbc:
11495
- return 'AES-CBC';
11496
- case DecrypterAesMode.ctr:
11497
- return 'AES-CTR';
11498
- default:
11499
- throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
11500
- }
11501
- }
11502
11426
 
11503
11427
  // PKCS7
11504
11428
  function removePadding(array) {
@@ -11748,8 +11672,7 @@ class Decrypter {
11748
11672
  this.currentIV = null;
11749
11673
  this.currentResult = null;
11750
11674
  this.useSoftware = void 0;
11751
- this.enableSoftwareAES = void 0;
11752
- this.enableSoftwareAES = config.enableSoftwareAES;
11675
+ this.useSoftware = config.enableSoftwareAES;
11753
11676
  this.removePKCS7Padding = removePKCS7Padding;
11754
11677
  // built in decryptor expects PKCS7 padding
11755
11678
  if (removePKCS7Padding) {
@@ -11762,7 +11685,9 @@ class Decrypter {
11762
11685
  /* no-op */
11763
11686
  }
11764
11687
  }
11765
- this.useSoftware = this.subtle === null;
11688
+ if (this.subtle === null) {
11689
+ this.useSoftware = true;
11690
+ }
11766
11691
  }
11767
11692
  destroy() {
11768
11693
  this.subtle = null;
@@ -11800,10 +11725,10 @@ class Decrypter {
11800
11725
  this.softwareDecrypter = null;
11801
11726
  }
11802
11727
  }
11803
- decrypt(data, key, iv, aesMode) {
11728
+ decrypt(data, key, iv) {
11804
11729
  if (this.useSoftware) {
11805
11730
  return new Promise((resolve, reject) => {
11806
- this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
11731
+ this.softwareDecrypt(new Uint8Array(data), key, iv);
11807
11732
  const decryptResult = this.flush();
11808
11733
  if (decryptResult) {
11809
11734
  resolve(decryptResult.buffer);
@@ -11812,21 +11737,17 @@ class Decrypter {
11812
11737
  }
11813
11738
  });
11814
11739
  }
11815
- return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
11740
+ return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
11816
11741
  }
11817
11742
 
11818
11743
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
11819
11744
  // data is handled in the flush() call
11820
- softwareDecrypt(data, key, iv, aesMode) {
11745
+ softwareDecrypt(data, key, iv) {
11821
11746
  const {
11822
11747
  currentIV,
11823
11748
  currentResult,
11824
11749
  remainderData
11825
11750
  } = this;
11826
- if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
11827
- logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
11828
- return null;
11829
- }
11830
11751
  this.logOnce('JS AES decrypt');
11831
11752
  // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
11832
11753
  // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
@@ -11859,11 +11780,11 @@ class Decrypter {
11859
11780
  }
11860
11781
  return result;
11861
11782
  }
11862
- webCryptoDecrypt(data, key, iv, aesMode) {
11783
+ webCryptoDecrypt(data, key, iv) {
11863
11784
  const subtle = this.subtle;
11864
11785
  if (this.key !== key || !this.fastAesKey) {
11865
11786
  this.key = key;
11866
- this.fastAesKey = new FastAESKey(subtle, key, aesMode);
11787
+ this.fastAesKey = new FastAESKey(subtle, key);
11867
11788
  }
11868
11789
  return this.fastAesKey.expandKey().then(aesKey => {
11869
11790
  // decrypt using web crypto
@@ -11871,25 +11792,22 @@ class Decrypter {
11871
11792
  return Promise.reject(new Error('web crypto not initialized'));
11872
11793
  }
11873
11794
  this.logOnce('WebCrypto AES decrypt');
11874
- const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
11795
+ const crypto = new AESCrypto(subtle, new Uint8Array(iv));
11875
11796
  return crypto.decrypt(data.buffer, aesKey);
11876
11797
  }).catch(err => {
11877
11798
  logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
11878
- return this.onWebCryptoError(data, key, iv, aesMode);
11799
+ return this.onWebCryptoError(data, key, iv);
11879
11800
  });
11880
11801
  }
11881
- onWebCryptoError(data, key, iv, aesMode) {
11882
- const enableSoftwareAES = this.enableSoftwareAES;
11883
- if (enableSoftwareAES) {
11884
- this.useSoftware = true;
11885
- this.logEnabled = true;
11886
- this.softwareDecrypt(data, key, iv, aesMode);
11887
- const decryptResult = this.flush();
11888
- if (decryptResult) {
11889
- return decryptResult.buffer;
11890
- }
11802
+ onWebCryptoError(data, key, iv) {
11803
+ this.useSoftware = true;
11804
+ this.logEnabled = true;
11805
+ this.softwareDecrypt(data, key, iv);
11806
+ const decryptResult = this.flush();
11807
+ if (decryptResult) {
11808
+ return decryptResult.buffer;
11891
11809
  }
11892
- throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
11810
+ throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
11893
11811
  }
11894
11812
  getValidChunk(data) {
11895
11813
  let currentChunk = data;
@@ -11940,7 +11858,7 @@ const State = {
11940
11858
  };
11941
11859
  class BaseStreamController extends TaskLoop {
11942
11860
  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
11943
- super(logPrefix, hls.logger);
11861
+ super();
11944
11862
  this.hls = void 0;
11945
11863
  this.fragPrevious = null;
11946
11864
  this.fragCurrent = null;
@@ -11965,98 +11883,22 @@ class BaseStreamController extends TaskLoop {
11965
11883
  this.startFragRequested = false;
11966
11884
  this.decrypter = void 0;
11967
11885
  this.initPTS = [];
11968
- this.buffering = true;
11969
- this.loadingParts = false;
11970
- this.onMediaSeeking = () => {
11971
- const {
11972
- config,
11973
- fragCurrent,
11974
- media,
11975
- mediaBuffer,
11976
- state
11977
- } = this;
11978
- const currentTime = media ? media.currentTime : 0;
11979
- const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
11980
- this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
11981
- if (this.state === State.ENDED) {
11982
- this.resetLoadingState();
11983
- } else if (fragCurrent) {
11984
- // Seeking while frag load is in progress
11985
- const tolerance = config.maxFragLookUpTolerance;
11986
- const fragStartOffset = fragCurrent.start - tolerance;
11987
- const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
11988
- // if seeking out of buffered range or into new one
11989
- if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
11990
- const pastFragment = currentTime > fragEndOffset;
11991
- // if the seek position is outside the current fragment range
11992
- if (currentTime < fragStartOffset || pastFragment) {
11993
- if (pastFragment && fragCurrent.loader) {
11994
- this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
11995
- fragCurrent.abortRequests();
11996
- this.resetLoadingState();
11997
- }
11998
- this.fragPrevious = null;
11999
- }
12000
- }
12001
- }
12002
- if (media) {
12003
- // Remove gap fragments
12004
- this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
12005
- this.lastCurrentTime = currentTime;
12006
- if (!this.loadingParts) {
12007
- const bufferEnd = Math.max(bufferInfo.end, currentTime);
12008
- const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
12009
- if (shouldLoadParts) {
12010
- this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
12011
- this.loadingParts = shouldLoadParts;
12012
- }
12013
- }
12014
- }
12015
-
12016
- // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
12017
- if (!this.loadedmetadata && !bufferInfo.len) {
12018
- this.nextLoadPosition = this.startPosition = currentTime;
12019
- }
12020
-
12021
- // Async tick to speed up processing
12022
- this.tickImmediate();
12023
- };
12024
- this.onMediaEnded = () => {
12025
- // reset startPosition and lastCurrentTime to restart playback @ stream beginning
12026
- this.startPosition = this.lastCurrentTime = 0;
12027
- if (this.playlistType === PlaylistLevelType.MAIN) {
12028
- this.hls.trigger(Events.MEDIA_ENDED, {
12029
- stalled: false
12030
- });
12031
- }
12032
- };
11886
+ this.onvseeking = null;
11887
+ this.onvended = null;
11888
+ this.logPrefix = '';
11889
+ this.log = void 0;
11890
+ this.warn = void 0;
12033
11891
  this.playlistType = playlistType;
11892
+ this.logPrefix = logPrefix;
11893
+ this.log = logger.log.bind(logger, `${logPrefix}:`);
11894
+ this.warn = logger.warn.bind(logger, `${logPrefix}:`);
12034
11895
  this.hls = hls;
12035
11896
  this.fragmentLoader = new FragmentLoader(hls.config);
12036
11897
  this.keyLoader = keyLoader;
12037
11898
  this.fragmentTracker = fragmentTracker;
12038
11899
  this.config = hls.config;
12039
11900
  this.decrypter = new Decrypter(hls.config);
12040
- }
12041
- registerListeners() {
12042
- const {
12043
- hls
12044
- } = this;
12045
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
12046
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
12047
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
12048
11901
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
12049
- hls.on(Events.ERROR, this.onError, this);
12050
- }
12051
- unregisterListeners() {
12052
- const {
12053
- hls
12054
- } = this;
12055
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
12056
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
12057
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
12058
- hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
12059
- hls.off(Events.ERROR, this.onError, this);
12060
11902
  }
12061
11903
  doTick() {
12062
11904
  this.onTickEnd();
@@ -12080,12 +11922,6 @@ class BaseStreamController extends TaskLoop {
12080
11922
  this.clearNextTick();
12081
11923
  this.state = State.STOPPED;
12082
11924
  }
12083
- pauseBuffering() {
12084
- this.buffering = false;
12085
- }
12086
- resumeBuffering() {
12087
- this.buffering = true;
12088
- }
12089
11925
  _streamEnded(bufferInfo, levelDetails) {
12090
11926
  // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
12091
11927
  // of nothing loading/loaded return false
@@ -12116,8 +11952,10 @@ class BaseStreamController extends TaskLoop {
12116
11952
  }
12117
11953
  onMediaAttached(event, data) {
12118
11954
  const media = this.media = this.mediaBuffer = data.media;
12119
- media.addEventListener('seeking', this.onMediaSeeking);
12120
- media.addEventListener('ended', this.onMediaEnded);
11955
+ this.onvseeking = this.onMediaSeeking.bind(this);
11956
+ this.onvended = this.onMediaEnded.bind(this);
11957
+ media.addEventListener('seeking', this.onvseeking);
11958
+ media.addEventListener('ended', this.onvended);
12121
11959
  const config = this.config;
12122
11960
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
12123
11961
  this.startLoad(config.startPosition);
@@ -12131,9 +11969,10 @@ class BaseStreamController extends TaskLoop {
12131
11969
  }
12132
11970
 
12133
11971
  // remove video listeners
12134
- if (media) {
12135
- media.removeEventListener('seeking', this.onMediaSeeking);
12136
- media.removeEventListener('ended', this.onMediaEnded);
11972
+ if (media && this.onvseeking && this.onvended) {
11973
+ media.removeEventListener('seeking', this.onvseeking);
11974
+ media.removeEventListener('ended', this.onvended);
11975
+ this.onvseeking = this.onvended = null;
12137
11976
  }
12138
11977
  if (this.keyLoader) {
12139
11978
  this.keyLoader.detach();
@@ -12143,8 +11982,56 @@ class BaseStreamController extends TaskLoop {
12143
11982
  this.fragmentTracker.removeAllFragments();
12144
11983
  this.stopLoad();
12145
11984
  }
12146
- onManifestLoading() {}
12147
- onError(event, data) {}
11985
+ onMediaSeeking() {
11986
+ const {
11987
+ config,
11988
+ fragCurrent,
11989
+ media,
11990
+ mediaBuffer,
11991
+ state
11992
+ } = this;
11993
+ const currentTime = media ? media.currentTime : 0;
11994
+ const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
11995
+ this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
11996
+ if (this.state === State.ENDED) {
11997
+ this.resetLoadingState();
11998
+ } else if (fragCurrent) {
11999
+ // Seeking while frag load is in progress
12000
+ const tolerance = config.maxFragLookUpTolerance;
12001
+ const fragStartOffset = fragCurrent.start - tolerance;
12002
+ const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
12003
+ // if seeking out of buffered range or into new one
12004
+ if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
12005
+ const pastFragment = currentTime > fragEndOffset;
12006
+ // if the seek position is outside the current fragment range
12007
+ if (currentTime < fragStartOffset || pastFragment) {
12008
+ if (pastFragment && fragCurrent.loader) {
12009
+ this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
12010
+ fragCurrent.abortRequests();
12011
+ this.resetLoadingState();
12012
+ }
12013
+ this.fragPrevious = null;
12014
+ }
12015
+ }
12016
+ }
12017
+ if (media) {
12018
+ // Remove gap fragments
12019
+ this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
12020
+ this.lastCurrentTime = currentTime;
12021
+ }
12022
+
12023
+ // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
12024
+ if (!this.loadedmetadata && !bufferInfo.len) {
12025
+ this.nextLoadPosition = this.startPosition = currentTime;
12026
+ }
12027
+
12028
+ // Async tick to speed up processing
12029
+ this.tickImmediate();
12030
+ }
12031
+ onMediaEnded() {
12032
+ // reset startPosition and lastCurrentTime to restart playback @ stream beginning
12033
+ this.startPosition = this.lastCurrentTime = 0;
12034
+ }
12148
12035
  onManifestLoaded(event, data) {
12149
12036
  this.startTimeOffset = data.startTimeOffset;
12150
12037
  this.initPTS = [];
@@ -12154,7 +12041,7 @@ class BaseStreamController extends TaskLoop {
12154
12041
  this.stopLoad();
12155
12042
  super.onHandlerDestroying();
12156
12043
  // @ts-ignore
12157
- this.hls = this.onMediaSeeking = this.onMediaEnded = null;
12044
+ this.hls = null;
12158
12045
  }
12159
12046
  onHandlerDestroyed() {
12160
12047
  this.state = State.STOPPED;
@@ -12285,10 +12172,10 @@ class BaseStreamController extends TaskLoop {
12285
12172
  const decryptData = frag.decryptdata;
12286
12173
 
12287
12174
  // check to see if the payload needs to be decrypted
12288
- if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
12175
+ if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
12289
12176
  const startTime = self.performance.now();
12290
12177
  // decrypt init segment data
12291
- return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
12178
+ return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
12292
12179
  hls.trigger(Events.ERROR, {
12293
12180
  type: ErrorTypes.MEDIA_ERROR,
12294
12181
  details: ErrorDetails.FRAG_DECRYPT_ERROR,
@@ -12400,7 +12287,7 @@ class BaseStreamController extends TaskLoop {
12400
12287
  }
12401
12288
  let keyLoadingPromise = null;
12402
12289
  if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
12403
- this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
12290
+ this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
12404
12291
  this.state = State.KEY_LOADING;
12405
12292
  this.fragCurrent = frag;
12406
12293
  keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
@@ -12421,16 +12308,8 @@ class BaseStreamController extends TaskLoop {
12421
12308
  } else if (!frag.encrypted && details.encryptedFragments.length) {
12422
12309
  this.keyLoader.loadClear(frag, details.encryptedFragments);
12423
12310
  }
12424
- const fragPrevious = this.fragPrevious;
12425
- if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
12426
- const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
12427
- if (shouldLoadParts !== this.loadingParts) {
12428
- this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
12429
- this.loadingParts = shouldLoadParts;
12430
- }
12431
- }
12432
12311
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
12433
- if (this.loadingParts && frag.sn !== 'initSegment') {
12312
+ if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
12434
12313
  const partList = details.partList;
12435
12314
  if (partList && progressCallback) {
12436
12315
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -12439,7 +12318,7 @@ class BaseStreamController extends TaskLoop {
12439
12318
  const partIndex = this.getNextPart(partList, frag, targetBufferTime);
12440
12319
  if (partIndex > -1) {
12441
12320
  const part = partList[partIndex];
12442
- 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))}`);
12321
+ 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))}`);
12443
12322
  this.nextLoadPosition = part.start + part.duration;
12444
12323
  this.state = State.FRAG_LOADING;
12445
12324
  let _result;
@@ -12468,14 +12347,7 @@ class BaseStreamController extends TaskLoop {
12468
12347
  }
12469
12348
  }
12470
12349
  }
12471
- if (frag.sn !== 'initSegment' && this.loadingParts) {
12472
- this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
12473
- this.loadingParts = false;
12474
- } else if (!frag.url) {
12475
- // Selected fragment hint for part but not loading parts
12476
- return Promise.resolve(null);
12477
- }
12478
- 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))}`);
12350
+ 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))}`);
12479
12351
  // Don't update nextLoadPosition for fragments which are not buffered
12480
12352
  if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
12481
12353
  this.nextLoadPosition = frag.start + frag.duration;
@@ -12573,36 +12445,8 @@ class BaseStreamController extends TaskLoop {
12573
12445
  if (part) {
12574
12446
  part.stats.parsing.end = now;
12575
12447
  }
12576
- // See if part loading should be disabled/enabled based on buffer and playback position.
12577
- if (frag.sn !== 'initSegment') {
12578
- const levelDetails = this.getLevelDetails();
12579
- const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
12580
- const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
12581
- if (shouldLoadParts !== this.loadingParts) {
12582
- this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
12583
- this.loadingParts = shouldLoadParts;
12584
- }
12585
- }
12586
12448
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
12587
12449
  }
12588
- shouldLoadParts(details, bufferEnd) {
12589
- if (this.config.lowLatencyMode) {
12590
- if (!details) {
12591
- return this.loadingParts;
12592
- }
12593
- if (details != null && details.partList) {
12594
- var _details$fragmentHint;
12595
- // Buffer must be ahead of first part + duration of parts after last segment
12596
- // and playback must be at or past segment adjacent to part list
12597
- const firstPart = details.partList[0];
12598
- const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
12599
- if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
12600
- return true;
12601
- }
12602
- }
12603
- }
12604
- return false;
12605
- }
12606
12450
  getCurrentContext(chunkMeta) {
12607
12451
  const {
12608
12452
  levels,
@@ -12751,8 +12595,7 @@ class BaseStreamController extends TaskLoop {
12751
12595
  config
12752
12596
  } = this;
12753
12597
  const start = fragments[0].start;
12754
- const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
12755
- let frag = null;
12598
+ let frag;
12756
12599
  if (levelDetails.live) {
12757
12600
  const initialLiveManifestSize = config.initialLiveManifestSize;
12758
12601
  if (fragLen < initialLiveManifestSize) {
@@ -12764,10 +12607,6 @@ class BaseStreamController extends TaskLoop {
12764
12607
  // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
12765
12608
  // we get the fragment matching that start time
12766
12609
  if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
12767
- if (canLoadParts && !this.loadingParts) {
12768
- this.log(`LL-Part loading ON for initial live fragment`);
12769
- this.loadingParts = true;
12770
- }
12771
12610
  frag = this.getInitialLiveFragment(levelDetails, fragments);
12772
12611
  this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
12773
12612
  }
@@ -12778,7 +12617,7 @@ class BaseStreamController extends TaskLoop {
12778
12617
 
12779
12618
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
12780
12619
  if (!frag) {
12781
- const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
12620
+ const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
12782
12621
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
12783
12622
  }
12784
12623
  return this.mapToInitFragWhenRequired(frag);
@@ -12900,7 +12739,7 @@ class BaseStreamController extends TaskLoop {
12900
12739
  } = levelDetails;
12901
12740
  const tolerance = config.maxFragLookUpTolerance;
12902
12741
  const partList = levelDetails.partList;
12903
- const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
12742
+ const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
12904
12743
  if (loadingParts && fragmentHint && !this.bitrateTest) {
12905
12744
  // Include incomplete fragment with parts at end
12906
12745
  fragments = fragments.concat(fragmentHint);
@@ -13093,7 +12932,7 @@ class BaseStreamController extends TaskLoop {
13093
12932
  errorAction.resolved = true;
13094
12933
  }
13095
12934
  } else {
13096
- this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
12935
+ logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
13097
12936
  return;
13098
12937
  }
13099
12938
  } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
@@ -13488,7 +13327,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
13488
13327
  */
13489
13328
  function getAudioConfig(observer, data, offset, audioCodec) {
13490
13329
  let adtsObjectType;
13491
- let originalAdtsObjectType;
13492
13330
  let adtsExtensionSamplingIndex;
13493
13331
  let adtsChannelConfig;
13494
13332
  let config;
@@ -13496,7 +13334,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13496
13334
  const manifestCodec = audioCodec;
13497
13335
  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
13498
13336
  // byte 2
13499
- adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
13337
+ adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
13500
13338
  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
13501
13339
  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
13502
13340
  const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
@@ -13513,8 +13351,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13513
13351
  // byte 3
13514
13352
  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
13515
13353
  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
13516
- // Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
13517
- if (/firefox|palemoon/i.test(userAgent)) {
13354
+ // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
13355
+ if (/firefox/i.test(userAgent)) {
13518
13356
  if (adtsSamplingIndex >= 6) {
13519
13357
  adtsObjectType = 5;
13520
13358
  config = new Array(4);
@@ -13608,7 +13446,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
13608
13446
  samplerate: adtsSamplingRates[adtsSamplingIndex],
13609
13447
  channelCount: adtsChannelConfig,
13610
13448
  codec: 'mp4a.40.' + adtsObjectType,
13611
- parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
13612
13449
  manifestCodec
13613
13450
  };
13614
13451
  }
@@ -13663,8 +13500,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
13663
13500
  track.channelCount = config.channelCount;
13664
13501
  track.codec = config.codec;
13665
13502
  track.manifestCodec = config.manifestCodec;
13666
- track.parsedCodec = config.parsedCodec;
13667
- logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
13503
+ logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
13668
13504
  }
13669
13505
  }
13670
13506
  function getFrameDuration(samplerate) {
@@ -14142,110 +13978,6 @@ class BaseVideoParser {
14142
13978
  logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
14143
13979
  }
14144
13980
  }
14145
- parseNALu(track, array) {
14146
- const len = array.byteLength;
14147
- let state = track.naluState || 0;
14148
- const lastState = state;
14149
- const units = [];
14150
- let i = 0;
14151
- let value;
14152
- let overflow;
14153
- let unitType;
14154
- let lastUnitStart = -1;
14155
- let lastUnitType = 0;
14156
- // logger.log('PES:' + Hex.hexDump(array));
14157
-
14158
- if (state === -1) {
14159
- // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
14160
- lastUnitStart = 0;
14161
- // NALu type is value read from offset 0
14162
- lastUnitType = this.getNALuType(array, 0);
14163
- state = 0;
14164
- i = 1;
14165
- }
14166
- while (i < len) {
14167
- value = array[i++];
14168
- // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
14169
- if (!state) {
14170
- state = value ? 0 : 1;
14171
- continue;
14172
- }
14173
- if (state === 1) {
14174
- state = value ? 0 : 2;
14175
- continue;
14176
- }
14177
- // here we have state either equal to 2 or 3
14178
- if (!value) {
14179
- state = 3;
14180
- } else if (value === 1) {
14181
- overflow = i - state - 1;
14182
- if (lastUnitStart >= 0) {
14183
- const unit = {
14184
- data: array.subarray(lastUnitStart, overflow),
14185
- type: lastUnitType
14186
- };
14187
- // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
14188
- units.push(unit);
14189
- } else {
14190
- // lastUnitStart is undefined => this is the first start code found in this PES packet
14191
- // first check if start code delimiter is overlapping between 2 PES packets,
14192
- // ie it started in last packet (lastState not zero)
14193
- // and ended at the beginning of this PES packet (i <= 4 - lastState)
14194
- const lastUnit = this.getLastNalUnit(track.samples);
14195
- if (lastUnit) {
14196
- if (lastState && i <= 4 - lastState) {
14197
- // start delimiter overlapping between PES packets
14198
- // strip start delimiter bytes from the end of last NAL unit
14199
- // check if lastUnit had a state different from zero
14200
- if (lastUnit.state) {
14201
- // strip last bytes
14202
- lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
14203
- }
14204
- }
14205
- // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
14206
-
14207
- if (overflow > 0) {
14208
- // logger.log('first NALU found with overflow:' + overflow);
14209
- lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
14210
- lastUnit.state = 0;
14211
- }
14212
- }
14213
- }
14214
- // check if we can read unit type
14215
- if (i < len) {
14216
- unitType = this.getNALuType(array, i);
14217
- // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
14218
- lastUnitStart = i;
14219
- lastUnitType = unitType;
14220
- state = 0;
14221
- } else {
14222
- // not enough byte to read unit type. let's read it on next PES parsing
14223
- state = -1;
14224
- }
14225
- } else {
14226
- state = 0;
14227
- }
14228
- }
14229
- if (lastUnitStart >= 0 && state >= 0) {
14230
- const unit = {
14231
- data: array.subarray(lastUnitStart, len),
14232
- type: lastUnitType,
14233
- state: state
14234
- };
14235
- units.push(unit);
14236
- // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
14237
- }
14238
- // no NALu found
14239
- if (units.length === 0) {
14240
- // append pes.data to previous NAL unit
14241
- const lastUnit = this.getLastNalUnit(track.samples);
14242
- if (lastUnit) {
14243
- lastUnit.data = appendUint8Array(lastUnit.data, array);
14244
- }
14245
- }
14246
- track.naluState = state;
14247
- return units;
14248
- }
14249
13981
  }
14250
13982
 
14251
13983
  /**
@@ -14388,11 +14120,194 @@ class ExpGolomb {
14388
14120
  readUInt() {
14389
14121
  return this.readBits(32);
14390
14122
  }
14123
+
14124
+ /**
14125
+ * Advance the ExpGolomb decoder past a scaling list. The scaling
14126
+ * list is optionally transmitted as part of a sequence parameter
14127
+ * set and is not relevant to transmuxing.
14128
+ * @param count the number of entries in this scaling list
14129
+ * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
14130
+ */
14131
+ skipScalingList(count) {
14132
+ let lastScale = 8;
14133
+ let nextScale = 8;
14134
+ let deltaScale;
14135
+ for (let j = 0; j < count; j++) {
14136
+ if (nextScale !== 0) {
14137
+ deltaScale = this.readEG();
14138
+ nextScale = (lastScale + deltaScale + 256) % 256;
14139
+ }
14140
+ lastScale = nextScale === 0 ? lastScale : nextScale;
14141
+ }
14142
+ }
14143
+
14144
+ /**
14145
+ * Read a sequence parameter set and return some interesting video
14146
+ * properties. A sequence parameter set is the H264 metadata that
14147
+ * describes the properties of upcoming video frames.
14148
+ * @returns an object with configuration parsed from the
14149
+ * sequence parameter set, including the dimensions of the
14150
+ * associated video frames.
14151
+ */
14152
+ readSPS() {
14153
+ let frameCropLeftOffset = 0;
14154
+ let frameCropRightOffset = 0;
14155
+ let frameCropTopOffset = 0;
14156
+ let frameCropBottomOffset = 0;
14157
+ let numRefFramesInPicOrderCntCycle;
14158
+ let scalingListCount;
14159
+ let i;
14160
+ const readUByte = this.readUByte.bind(this);
14161
+ const readBits = this.readBits.bind(this);
14162
+ const readUEG = this.readUEG.bind(this);
14163
+ const readBoolean = this.readBoolean.bind(this);
14164
+ const skipBits = this.skipBits.bind(this);
14165
+ const skipEG = this.skipEG.bind(this);
14166
+ const skipUEG = this.skipUEG.bind(this);
14167
+ const skipScalingList = this.skipScalingList.bind(this);
14168
+ readUByte();
14169
+ const profileIdc = readUByte(); // profile_idc
14170
+ readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
14171
+ skipBits(3); // reserved_zero_3bits u(3),
14172
+ readUByte(); // level_idc u(8)
14173
+ skipUEG(); // seq_parameter_set_id
14174
+ // some profiles have more optional data we don't need
14175
+ if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
14176
+ const chromaFormatIdc = readUEG();
14177
+ if (chromaFormatIdc === 3) {
14178
+ skipBits(1);
14179
+ } // separate_colour_plane_flag
14180
+
14181
+ skipUEG(); // bit_depth_luma_minus8
14182
+ skipUEG(); // bit_depth_chroma_minus8
14183
+ skipBits(1); // qpprime_y_zero_transform_bypass_flag
14184
+ if (readBoolean()) {
14185
+ // seq_scaling_matrix_present_flag
14186
+ scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
14187
+ for (i = 0; i < scalingListCount; i++) {
14188
+ if (readBoolean()) {
14189
+ // seq_scaling_list_present_flag[ i ]
14190
+ if (i < 6) {
14191
+ skipScalingList(16);
14192
+ } else {
14193
+ skipScalingList(64);
14194
+ }
14195
+ }
14196
+ }
14197
+ }
14198
+ }
14199
+ skipUEG(); // log2_max_frame_num_minus4
14200
+ const picOrderCntType = readUEG();
14201
+ if (picOrderCntType === 0) {
14202
+ readUEG(); // log2_max_pic_order_cnt_lsb_minus4
14203
+ } else if (picOrderCntType === 1) {
14204
+ skipBits(1); // delta_pic_order_always_zero_flag
14205
+ skipEG(); // offset_for_non_ref_pic
14206
+ skipEG(); // offset_for_top_to_bottom_field
14207
+ numRefFramesInPicOrderCntCycle = readUEG();
14208
+ for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
14209
+ skipEG();
14210
+ } // offset_for_ref_frame[ i ]
14211
+ }
14212
+ skipUEG(); // max_num_ref_frames
14213
+ skipBits(1); // gaps_in_frame_num_value_allowed_flag
14214
+ const picWidthInMbsMinus1 = readUEG();
14215
+ const picHeightInMapUnitsMinus1 = readUEG();
14216
+ const frameMbsOnlyFlag = readBits(1);
14217
+ if (frameMbsOnlyFlag === 0) {
14218
+ skipBits(1);
14219
+ } // mb_adaptive_frame_field_flag
14220
+
14221
+ skipBits(1); // direct_8x8_inference_flag
14222
+ if (readBoolean()) {
14223
+ // frame_cropping_flag
14224
+ frameCropLeftOffset = readUEG();
14225
+ frameCropRightOffset = readUEG();
14226
+ frameCropTopOffset = readUEG();
14227
+ frameCropBottomOffset = readUEG();
14228
+ }
14229
+ let pixelRatio = [1, 1];
14230
+ if (readBoolean()) {
14231
+ // vui_parameters_present_flag
14232
+ if (readBoolean()) {
14233
+ // aspect_ratio_info_present_flag
14234
+ const aspectRatioIdc = readUByte();
14235
+ switch (aspectRatioIdc) {
14236
+ case 1:
14237
+ pixelRatio = [1, 1];
14238
+ break;
14239
+ case 2:
14240
+ pixelRatio = [12, 11];
14241
+ break;
14242
+ case 3:
14243
+ pixelRatio = [10, 11];
14244
+ break;
14245
+ case 4:
14246
+ pixelRatio = [16, 11];
14247
+ break;
14248
+ case 5:
14249
+ pixelRatio = [40, 33];
14250
+ break;
14251
+ case 6:
14252
+ pixelRatio = [24, 11];
14253
+ break;
14254
+ case 7:
14255
+ pixelRatio = [20, 11];
14256
+ break;
14257
+ case 8:
14258
+ pixelRatio = [32, 11];
14259
+ break;
14260
+ case 9:
14261
+ pixelRatio = [80, 33];
14262
+ break;
14263
+ case 10:
14264
+ pixelRatio = [18, 11];
14265
+ break;
14266
+ case 11:
14267
+ pixelRatio = [15, 11];
14268
+ break;
14269
+ case 12:
14270
+ pixelRatio = [64, 33];
14271
+ break;
14272
+ case 13:
14273
+ pixelRatio = [160, 99];
14274
+ break;
14275
+ case 14:
14276
+ pixelRatio = [4, 3];
14277
+ break;
14278
+ case 15:
14279
+ pixelRatio = [3, 2];
14280
+ break;
14281
+ case 16:
14282
+ pixelRatio = [2, 1];
14283
+ break;
14284
+ case 255:
14285
+ {
14286
+ pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
14287
+ break;
14288
+ }
14289
+ }
14290
+ }
14291
+ }
14292
+ return {
14293
+ width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
14294
+ height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
14295
+ pixelRatio: pixelRatio
14296
+ };
14297
+ }
14298
+ readSliceType() {
14299
+ // skip NALu type
14300
+ this.readUByte();
14301
+ // discard first_mb_in_slice
14302
+ this.readUEG();
14303
+ // return slice_type
14304
+ return this.readUEG();
14305
+ }
14391
14306
  }
14392
14307
 
14393
14308
  class AvcVideoParser extends BaseVideoParser {
14394
- parsePES(track, textTrack, pes, last, duration) {
14395
- const units = this.parseNALu(track, pes.data);
14309
+ parseAVCPES(track, textTrack, pes, last, duration) {
14310
+ const units = this.parseAVCNALu(track, pes.data);
14396
14311
  let VideoSample = this.VideoSample;
14397
14312
  let push;
14398
14313
  let spsfound = false;
@@ -14417,7 +14332,7 @@ class AvcVideoParser extends BaseVideoParser {
14417
14332
  // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
14418
14333
  if (spsfound && data.length > 4) {
14419
14334
  // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
14420
- const sliceType = this.readSliceType(data);
14335
+ const sliceType = new ExpGolomb(data).readSliceType();
14421
14336
  // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
14422
14337
  // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
14423
14338
  // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
@@ -14471,7 +14386,8 @@ class AvcVideoParser extends BaseVideoParser {
14471
14386
  push = true;
14472
14387
  spsfound = true;
14473
14388
  const sps = unit.data;
14474
- const config = this.readSPS(sps);
14389
+ const expGolombDecoder = new ExpGolomb(sps);
14390
+ const config = expGolombDecoder.readSPS();
14475
14391
  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]) {
14476
14392
  track.width = config.width;
14477
14393
  track.height = config.height;
@@ -14527,192 +14443,109 @@ class AvcVideoParser extends BaseVideoParser {
14527
14443
  this.VideoSample = null;
14528
14444
  }
14529
14445
  }
14530
- getNALuType(data, offset) {
14531
- return data[offset] & 0x1f;
14532
- }
14533
- readSliceType(data) {
14534
- const eg = new ExpGolomb(data);
14535
- // skip NALu type
14536
- eg.readUByte();
14537
- // discard first_mb_in_slice
14538
- eg.readUEG();
14539
- // return slice_type
14540
- return eg.readUEG();
14541
- }
14446
+ parseAVCNALu(track, array) {
14447
+ const len = array.byteLength;
14448
+ let state = track.naluState || 0;
14449
+ const lastState = state;
14450
+ const units = [];
14451
+ let i = 0;
14452
+ let value;
14453
+ let overflow;
14454
+ let unitType;
14455
+ let lastUnitStart = -1;
14456
+ let lastUnitType = 0;
14457
+ // logger.log('PES:' + Hex.hexDump(array));
14542
14458
 
14543
- /**
14544
- * The scaling list is optionally transmitted as part of a sequence parameter
14545
- * set and is not relevant to transmuxing.
14546
- * @param count the number of entries in this scaling list
14547
- * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
14548
- */
14549
- skipScalingList(count, reader) {
14550
- let lastScale = 8;
14551
- let nextScale = 8;
14552
- let deltaScale;
14553
- for (let j = 0; j < count; j++) {
14554
- if (nextScale !== 0) {
14555
- deltaScale = reader.readEG();
14556
- nextScale = (lastScale + deltaScale + 256) % 256;
14557
- }
14558
- lastScale = nextScale === 0 ? lastScale : nextScale;
14459
+ if (state === -1) {
14460
+ // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
14461
+ lastUnitStart = 0;
14462
+ // NALu type is value read from offset 0
14463
+ lastUnitType = array[0] & 0x1f;
14464
+ state = 0;
14465
+ i = 1;
14559
14466
  }
14560
- }
14561
-
14562
- /**
14563
- * Read a sequence parameter set and return some interesting video
14564
- * properties. A sequence parameter set is the H264 metadata that
14565
- * describes the properties of upcoming video frames.
14566
- * @returns an object with configuration parsed from the
14567
- * sequence parameter set, including the dimensions of the
14568
- * associated video frames.
14569
- */
14570
- readSPS(sps) {
14571
- const eg = new ExpGolomb(sps);
14572
- let frameCropLeftOffset = 0;
14573
- let frameCropRightOffset = 0;
14574
- let frameCropTopOffset = 0;
14575
- let frameCropBottomOffset = 0;
14576
- let numRefFramesInPicOrderCntCycle;
14577
- let scalingListCount;
14578
- let i;
14579
- const readUByte = eg.readUByte.bind(eg);
14580
- const readBits = eg.readBits.bind(eg);
14581
- const readUEG = eg.readUEG.bind(eg);
14582
- const readBoolean = eg.readBoolean.bind(eg);
14583
- const skipBits = eg.skipBits.bind(eg);
14584
- const skipEG = eg.skipEG.bind(eg);
14585
- const skipUEG = eg.skipUEG.bind(eg);
14586
- const skipScalingList = this.skipScalingList.bind(this);
14587
- readUByte();
14588
- const profileIdc = readUByte(); // profile_idc
14589
- readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
14590
- skipBits(3); // reserved_zero_3bits u(3),
14591
- readUByte(); // level_idc u(8)
14592
- skipUEG(); // seq_parameter_set_id
14593
- // some profiles have more optional data we don't need
14594
- if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
14595
- const chromaFormatIdc = readUEG();
14596
- if (chromaFormatIdc === 3) {
14597
- skipBits(1);
14598
- } // separate_colour_plane_flag
14599
-
14600
- skipUEG(); // bit_depth_luma_minus8
14601
- skipUEG(); // bit_depth_chroma_minus8
14602
- skipBits(1); // qpprime_y_zero_transform_bypass_flag
14603
- if (readBoolean()) {
14604
- // seq_scaling_matrix_present_flag
14605
- scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
14606
- for (i = 0; i < scalingListCount; i++) {
14607
- if (readBoolean()) {
14608
- // seq_scaling_list_present_flag[ i ]
14609
- if (i < 6) {
14610
- skipScalingList(16, eg);
14611
- } else {
14612
- skipScalingList(64, eg);
14467
+ while (i < len) {
14468
+ value = array[i++];
14469
+ // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
14470
+ if (!state) {
14471
+ state = value ? 0 : 1;
14472
+ continue;
14473
+ }
14474
+ if (state === 1) {
14475
+ state = value ? 0 : 2;
14476
+ continue;
14477
+ }
14478
+ // here we have state either equal to 2 or 3
14479
+ if (!value) {
14480
+ state = 3;
14481
+ } else if (value === 1) {
14482
+ overflow = i - state - 1;
14483
+ if (lastUnitStart >= 0) {
14484
+ const unit = {
14485
+ data: array.subarray(lastUnitStart, overflow),
14486
+ type: lastUnitType
14487
+ };
14488
+ // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
14489
+ units.push(unit);
14490
+ } else {
14491
+ // lastUnitStart is undefined => this is the first start code found in this PES packet
14492
+ // first check if start code delimiter is overlapping between 2 PES packets,
14493
+ // ie it started in last packet (lastState not zero)
14494
+ // and ended at the beginning of this PES packet (i <= 4 - lastState)
14495
+ const lastUnit = this.getLastNalUnit(track.samples);
14496
+ if (lastUnit) {
14497
+ if (lastState && i <= 4 - lastState) {
14498
+ // start delimiter overlapping between PES packets
14499
+ // strip start delimiter bytes from the end of last NAL unit
14500
+ // check if lastUnit had a state different from zero
14501
+ if (lastUnit.state) {
14502
+ // strip last bytes
14503
+ lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
14504
+ }
14505
+ }
14506
+ // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
14507
+
14508
+ if (overflow > 0) {
14509
+ // logger.log('first NALU found with overflow:' + overflow);
14510
+ lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
14511
+ lastUnit.state = 0;
14613
14512
  }
14614
14513
  }
14615
14514
  }
14515
+ // check if we can read unit type
14516
+ if (i < len) {
14517
+ unitType = array[i] & 0x1f;
14518
+ // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
14519
+ lastUnitStart = i;
14520
+ lastUnitType = unitType;
14521
+ state = 0;
14522
+ } else {
14523
+ // not enough byte to read unit type. let's read it on next PES parsing
14524
+ state = -1;
14525
+ }
14526
+ } else {
14527
+ state = 0;
14616
14528
  }
14617
14529
  }
14618
- skipUEG(); // log2_max_frame_num_minus4
14619
- const picOrderCntType = readUEG();
14620
- if (picOrderCntType === 0) {
14621
- readUEG(); // log2_max_pic_order_cnt_lsb_minus4
14622
- } else if (picOrderCntType === 1) {
14623
- skipBits(1); // delta_pic_order_always_zero_flag
14624
- skipEG(); // offset_for_non_ref_pic
14625
- skipEG(); // offset_for_top_to_bottom_field
14626
- numRefFramesInPicOrderCntCycle = readUEG();
14627
- for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
14628
- skipEG();
14629
- } // offset_for_ref_frame[ i ]
14630
- }
14631
- skipUEG(); // max_num_ref_frames
14632
- skipBits(1); // gaps_in_frame_num_value_allowed_flag
14633
- const picWidthInMbsMinus1 = readUEG();
14634
- const picHeightInMapUnitsMinus1 = readUEG();
14635
- const frameMbsOnlyFlag = readBits(1);
14636
- if (frameMbsOnlyFlag === 0) {
14637
- skipBits(1);
14638
- } // mb_adaptive_frame_field_flag
14639
-
14640
- skipBits(1); // direct_8x8_inference_flag
14641
- if (readBoolean()) {
14642
- // frame_cropping_flag
14643
- frameCropLeftOffset = readUEG();
14644
- frameCropRightOffset = readUEG();
14645
- frameCropTopOffset = readUEG();
14646
- frameCropBottomOffset = readUEG();
14530
+ if (lastUnitStart >= 0 && state >= 0) {
14531
+ const unit = {
14532
+ data: array.subarray(lastUnitStart, len),
14533
+ type: lastUnitType,
14534
+ state: state
14535
+ };
14536
+ units.push(unit);
14537
+ // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
14647
14538
  }
14648
- let pixelRatio = [1, 1];
14649
- if (readBoolean()) {
14650
- // vui_parameters_present_flag
14651
- if (readBoolean()) {
14652
- // aspect_ratio_info_present_flag
14653
- const aspectRatioIdc = readUByte();
14654
- switch (aspectRatioIdc) {
14655
- case 1:
14656
- pixelRatio = [1, 1];
14657
- break;
14658
- case 2:
14659
- pixelRatio = [12, 11];
14660
- break;
14661
- case 3:
14662
- pixelRatio = [10, 11];
14663
- break;
14664
- case 4:
14665
- pixelRatio = [16, 11];
14666
- break;
14667
- case 5:
14668
- pixelRatio = [40, 33];
14669
- break;
14670
- case 6:
14671
- pixelRatio = [24, 11];
14672
- break;
14673
- case 7:
14674
- pixelRatio = [20, 11];
14675
- break;
14676
- case 8:
14677
- pixelRatio = [32, 11];
14678
- break;
14679
- case 9:
14680
- pixelRatio = [80, 33];
14681
- break;
14682
- case 10:
14683
- pixelRatio = [18, 11];
14684
- break;
14685
- case 11:
14686
- pixelRatio = [15, 11];
14687
- break;
14688
- case 12:
14689
- pixelRatio = [64, 33];
14690
- break;
14691
- case 13:
14692
- pixelRatio = [160, 99];
14693
- break;
14694
- case 14:
14695
- pixelRatio = [4, 3];
14696
- break;
14697
- case 15:
14698
- pixelRatio = [3, 2];
14699
- break;
14700
- case 16:
14701
- pixelRatio = [2, 1];
14702
- break;
14703
- case 255:
14704
- {
14705
- pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
14706
- break;
14707
- }
14708
- }
14539
+ // no NALu found
14540
+ if (units.length === 0) {
14541
+ // append pes.data to previous NAL unit
14542
+ const lastUnit = this.getLastNalUnit(track.samples);
14543
+ if (lastUnit) {
14544
+ lastUnit.data = appendUint8Array(lastUnit.data, array);
14709
14545
  }
14710
14546
  }
14711
- return {
14712
- width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
14713
- height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
14714
- pixelRatio: pixelRatio
14715
- };
14547
+ track.naluState = state;
14548
+ return units;
14716
14549
  }
14717
14550
  }
14718
14551
 
@@ -14730,7 +14563,7 @@ class SampleAesDecrypter {
14730
14563
  });
14731
14564
  }
14732
14565
  decryptBuffer(encryptedData) {
14733
- return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
14566
+ return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
14734
14567
  }
14735
14568
 
14736
14569
  // AAC - encrypt all full 16 bytes blocks starting from offset 16
@@ -14844,7 +14677,7 @@ class TSDemuxer {
14844
14677
  this.observer = observer;
14845
14678
  this.config = config;
14846
14679
  this.typeSupported = typeSupported;
14847
- this.videoParser = null;
14680
+ this.videoParser = new AvcVideoParser();
14848
14681
  }
14849
14682
  static probe(data) {
14850
14683
  const syncOffset = TSDemuxer.syncOffset(data);
@@ -15009,16 +14842,7 @@ class TSDemuxer {
15009
14842
  case videoPid:
15010
14843
  if (stt) {
15011
14844
  if (videoData && (pes = parsePES(videoData))) {
15012
- if (this.videoParser === null) {
15013
- switch (videoTrack.segmentCodec) {
15014
- case 'avc':
15015
- this.videoParser = new AvcVideoParser();
15016
- break;
15017
- }
15018
- }
15019
- if (this.videoParser !== null) {
15020
- this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
15021
- }
14845
+ this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
15022
14846
  }
15023
14847
  videoData = {
15024
14848
  data: [],
@@ -15180,17 +15004,8 @@ class TSDemuxer {
15180
15004
  // try to parse last PES packets
15181
15005
  let pes;
15182
15006
  if (videoData && (pes = parsePES(videoData))) {
15183
- if (this.videoParser === null) {
15184
- switch (videoTrack.segmentCodec) {
15185
- case 'avc':
15186
- this.videoParser = new AvcVideoParser();
15187
- break;
15188
- }
15189
- }
15190
- if (this.videoParser !== null) {
15191
- this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
15192
- videoTrack.pesData = null;
15193
- }
15007
+ this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
15008
+ videoTrack.pesData = null;
15194
15009
  } else {
15195
15010
  // either avcData null or PES truncated, keep it for next frag parsing
15196
15011
  videoTrack.pesData = videoData;
@@ -15493,10 +15308,7 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
15493
15308
  logger.warn('Unsupported EC-3 in M2TS found');
15494
15309
  break;
15495
15310
  case 0x24:
15496
- // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
15497
- {
15498
- logger.warn('Unsupported HEVC in M2TS found');
15499
- }
15311
+ logger.warn('Unsupported HEVC in M2TS found');
15500
15312
  break;
15501
15313
  }
15502
15314
  // move to the next table entry
@@ -15719,8 +15531,6 @@ class MP4 {
15719
15531
  avc1: [],
15720
15532
  // codingname
15721
15533
  avcC: [],
15722
- hvc1: [],
15723
- hvcC: [],
15724
15534
  btrt: [],
15725
15535
  dinf: [],
15726
15536
  dref: [],
@@ -16145,10 +15955,8 @@ class MP4 {
16145
15955
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
16146
15956
  }
16147
15957
  return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
16148
- } else if (track.segmentCodec === 'avc') {
16149
- return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
16150
15958
  } else {
16151
- return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
15959
+ return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
16152
15960
  }
16153
15961
  }
16154
15962
  static tkhd(track) {
@@ -16286,84 +16094,6 @@ class MP4 {
16286
16094
  const result = appendUint8Array(MP4.FTYP, movie);
16287
16095
  return result;
16288
16096
  }
16289
- static hvc1(track) {
16290
- const ps = track.params;
16291
- const units = [track.vps, track.sps, track.pps];
16292
- const NALuLengthSize = 4;
16293
- 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]);
16294
-
16295
- // compute hvcC size in bytes
16296
- let length = config.length;
16297
- for (let i = 0; i < units.length; i += 1) {
16298
- length += 3;
16299
- for (let j = 0; j < units[i].length; j += 1) {
16300
- length += 2 + units[i][j].length;
16301
- }
16302
- }
16303
- const hvcC = new Uint8Array(length);
16304
- hvcC.set(config, 0);
16305
- length = config.length;
16306
- // append parameter set units: one vps, one or more sps and pps
16307
- const iMax = units.length - 1;
16308
- for (let i = 0; i < units.length; i += 1) {
16309
- hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
16310
- length += 3;
16311
- for (let j = 0; j < units[i].length; j += 1) {
16312
- hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
16313
- length += 2;
16314
- hvcC.set(units[i][j], length);
16315
- length += units[i][j].length;
16316
- }
16317
- }
16318
- const hvcc = MP4.box(MP4.types.hvcC, hvcC);
16319
- const width = track.width;
16320
- const height = track.height;
16321
- const hSpacing = track.pixelRatio[0];
16322
- const vSpacing = track.pixelRatio[1];
16323
- return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
16324
- // reserved
16325
- 0x00, 0x00, 0x00,
16326
- // reserved
16327
- 0x00, 0x01,
16328
- // data_reference_index
16329
- 0x00, 0x00,
16330
- // pre_defined
16331
- 0x00, 0x00,
16332
- // reserved
16333
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
16334
- // pre_defined
16335
- width >> 8 & 0xff, width & 0xff,
16336
- // width
16337
- height >> 8 & 0xff, height & 0xff,
16338
- // height
16339
- 0x00, 0x48, 0x00, 0x00,
16340
- // horizresolution
16341
- 0x00, 0x48, 0x00, 0x00,
16342
- // vertresolution
16343
- 0x00, 0x00, 0x00, 0x00,
16344
- // reserved
16345
- 0x00, 0x01,
16346
- // frame_count
16347
- 0x12, 0x64, 0x61, 0x69, 0x6c,
16348
- // dailymotion/hls.js
16349
- 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,
16350
- // compressorname
16351
- 0x00, 0x18,
16352
- // depth = 24
16353
- 0x11, 0x11]),
16354
- // pre_defined = -1
16355
- hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
16356
- // bufferSizeDB
16357
- 0x00, 0x2d, 0xc6, 0xc0,
16358
- // maxBitrate
16359
- 0x00, 0x2d, 0xc6, 0xc0])),
16360
- // avgBitrate
16361
- MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
16362
- // hSpacing
16363
- hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
16364
- // vSpacing
16365
- vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
16366
- }
16367
16097
  }
16368
16098
  MP4.types = void 0;
16369
16099
  MP4.HDLR_TYPES = void 0;
@@ -16739,9 +16469,9 @@ class MP4Remuxer {
16739
16469
  const foundOverlap = delta < -1;
16740
16470
  if (foundHole || foundOverlap) {
16741
16471
  if (foundHole) {
16742
- logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
16472
+ logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
16743
16473
  } else {
16744
- logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
16474
+ logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
16745
16475
  }
16746
16476
  if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
16747
16477
  firstDTS = nextAvcDts;
@@ -16750,24 +16480,12 @@ class MP4Remuxer {
16750
16480
  inputSamples[0].dts = firstDTS;
16751
16481
  inputSamples[0].pts = firstPTS;
16752
16482
  } else {
16753
- let isPTSOrderRetained = true;
16754
16483
  for (let i = 0; i < inputSamples.length; i++) {
16755
- if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
16484
+ if (inputSamples[i].dts > firstPTS) {
16756
16485
  break;
16757
16486
  }
16758
- const prevPTS = inputSamples[i].pts;
16759
16487
  inputSamples[i].dts -= delta;
16760
16488
  inputSamples[i].pts -= delta;
16761
-
16762
- // check to see if this sample's PTS order has changed
16763
- // relative to the next one
16764
- if (i < inputSamples.length - 1) {
16765
- const nextSamplePTS = inputSamples[i + 1].pts;
16766
- const currentSamplePTS = inputSamples[i].pts;
16767
- const currentOrder = nextSamplePTS <= currentSamplePTS;
16768
- const prevOrder = nextSamplePTS <= prevPTS;
16769
- isPTSOrderRetained = currentOrder == prevOrder;
16770
- }
16771
16489
  }
16772
16490
  }
16773
16491
  logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
@@ -16915,7 +16633,7 @@ class MP4Remuxer {
16915
16633
  }
16916
16634
  }
16917
16635
  }
16918
- // next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
16636
+ // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
16919
16637
  mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
16920
16638
  this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
16921
16639
  this.videoSampleDuration = mp4SampleDuration;
@@ -17048,7 +16766,7 @@ class MP4Remuxer {
17048
16766
  logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
17049
16767
  for (let j = 0; j < missing; j++) {
17050
16768
  const newStamp = Math.max(nextPts, 0);
17051
- let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
16769
+ let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
17052
16770
  if (!fillFrame) {
17053
16771
  logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
17054
16772
  fillFrame = sample.unit.subarray();
@@ -17176,7 +16894,7 @@ class MP4Remuxer {
17176
16894
  // samples count of this segment's duration
17177
16895
  const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
17178
16896
  // silent frame
17179
- const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
16897
+ const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
17180
16898
  logger.warn('[mp4-remuxer]: remux empty Audio');
17181
16899
  // Can't remux if we can't generate a silent frame...
17182
16900
  if (!silentFrame) {
@@ -17567,15 +17285,13 @@ class Transmuxer {
17567
17285
  initSegmentData
17568
17286
  } = transmuxConfig;
17569
17287
  const keyData = getEncryptionType(uintData, decryptdata);
17570
- if (keyData && isFullSegmentEncryption(keyData.method)) {
17288
+ if (keyData && keyData.method === 'AES-128') {
17571
17289
  const decrypter = this.getDecrypter();
17572
- const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
17573
-
17574
17290
  // Software decryption is synchronous; webCrypto is not
17575
17291
  if (decrypter.isSync()) {
17576
17292
  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
17577
17293
  // data is handled in the flush() call
17578
- let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
17294
+ let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
17579
17295
  // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
17580
17296
  const loadingParts = chunkMeta.part > -1;
17581
17297
  if (loadingParts) {
@@ -17587,7 +17303,7 @@ class Transmuxer {
17587
17303
  }
17588
17304
  uintData = new Uint8Array(decryptedData);
17589
17305
  } else {
17590
- this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
17306
+ this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
17591
17307
  // Calling push here is important; if flush() is called while this is still resolving, this ensures that
17592
17308
  // the decrypted data has been transmuxed
17593
17309
  const result = this.push(decryptedData, null, chunkMeta);
@@ -18241,7 +17957,14 @@ class TransmuxerInterface {
18241
17957
  this.observer = new EventEmitter();
18242
17958
  this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
18243
17959
  this.observer.on(Events.ERROR, forwardMessage);
18244
- const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
17960
+ const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
17961
+ isTypeSupported: () => false
17962
+ };
17963
+ const m2tsTypeSupported = {
17964
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
17965
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
17966
+ ac3: false
17967
+ };
18245
17968
 
18246
17969
  // navigator.vendor is not always available in Web Worker
18247
17970
  // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
@@ -18505,9 +18228,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
18505
18228
  const MAX_START_GAP_JUMP = 2.0;
18506
18229
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
18507
18230
  const SKIP_BUFFER_RANGE_START = 0.05;
18508
- class GapController extends Logger {
18231
+ class GapController {
18509
18232
  constructor(config, media, fragmentTracker, hls) {
18510
- super('gap-controller', hls.logger);
18511
18233
  this.config = void 0;
18512
18234
  this.media = null;
18513
18235
  this.fragmentTracker = void 0;
@@ -18517,7 +18239,6 @@ class GapController extends Logger {
18517
18239
  this.stalled = null;
18518
18240
  this.moved = false;
18519
18241
  this.seeking = false;
18520
- this.ended = 0;
18521
18242
  this.config = config;
18522
18243
  this.media = media;
18523
18244
  this.fragmentTracker = fragmentTracker;
@@ -18535,7 +18256,7 @@ class GapController extends Logger {
18535
18256
  *
18536
18257
  * @param lastCurrentTime - Previously read playhead position
18537
18258
  */
18538
- poll(lastCurrentTime, activeFrag, levelDetails, state) {
18259
+ poll(lastCurrentTime, activeFrag) {
18539
18260
  const {
18540
18261
  config,
18541
18262
  media,
@@ -18554,7 +18275,6 @@ class GapController extends Logger {
18554
18275
 
18555
18276
  // The playhead is moving, no-op
18556
18277
  if (currentTime !== lastCurrentTime) {
18557
- this.ended = 0;
18558
18278
  this.moved = true;
18559
18279
  if (!seeking) {
18560
18280
  this.nudgeRetry = 0;
@@ -18563,7 +18283,7 @@ class GapController extends Logger {
18563
18283
  // The playhead is now moving, but was previously stalled
18564
18284
  if (this.stallReported) {
18565
18285
  const _stalledDuration = self.performance.now() - stalled;
18566
- this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
18286
+ logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
18567
18287
  this.stallReported = false;
18568
18288
  }
18569
18289
  this.stalled = null;
@@ -18599,6 +18319,7 @@ class GapController extends Logger {
18599
18319
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
18600
18320
  // The addition poll gives the browser a chance to jump the gap for us
18601
18321
  if (!this.moved && this.stalled !== null) {
18322
+ var _level$details;
18602
18323
  // There is no playable buffer (seeked, waiting for buffer)
18603
18324
  const isBuffered = bufferInfo.len > 0;
18604
18325
  if (!isBuffered && !nextStart) {
@@ -18610,8 +18331,9 @@ class GapController extends Logger {
18610
18331
  // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
18611
18332
  // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
18612
18333
  // that begins over 1 target duration after the video start position.
18613
- const isLive = !!(levelDetails != null && levelDetails.live);
18614
- const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
18334
+ const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
18335
+ const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
18336
+ const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
18615
18337
  const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
18616
18338
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
18617
18339
  if (!media.paused) {
@@ -18629,17 +18351,6 @@ class GapController extends Logger {
18629
18351
  }
18630
18352
  const stalledDuration = tnow - stalled;
18631
18353
  if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
18632
- // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
18633
- if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
18634
- if (stalledDuration < 1000 || this.ended) {
18635
- return;
18636
- }
18637
- this.ended = currentTime;
18638
- this.hls.trigger(Events.MEDIA_ENDED, {
18639
- stalled: true
18640
- });
18641
- return;
18642
- }
18643
18354
  // Report stalling after trying to fix
18644
18355
  this._reportStall(bufferInfo);
18645
18356
  if (!this.media) {
@@ -18683,7 +18394,7 @@ class GapController extends Logger {
18683
18394
  // needs to cross some sort of threshold covering all source-buffers content
18684
18395
  // to start playing properly.
18685
18396
  if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
18686
- this.warn('Trying to nudge playhead over buffer-hole');
18397
+ logger.warn('Trying to nudge playhead over buffer-hole');
18687
18398
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
18688
18399
  // We only try to jump the hole if it's under the configured size
18689
18400
  // Reset stalled so to rearm watchdog timer
@@ -18707,7 +18418,7 @@ class GapController extends Logger {
18707
18418
  // Report stalled error once
18708
18419
  this.stallReported = true;
18709
18420
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
18710
- this.warn(error.message);
18421
+ logger.warn(error.message);
18711
18422
  hls.trigger(Events.ERROR, {
18712
18423
  type: ErrorTypes.MEDIA_ERROR,
18713
18424
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -18775,7 +18486,7 @@ class GapController extends Logger {
18775
18486
  }
18776
18487
  }
18777
18488
  const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
18778
- this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
18489
+ logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
18779
18490
  this.moved = true;
18780
18491
  this.stalled = null;
18781
18492
  media.currentTime = targetTime;
@@ -18816,7 +18527,7 @@ class GapController extends Logger {
18816
18527
  const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
18817
18528
  // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
18818
18529
  const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
18819
- this.warn(error.message);
18530
+ logger.warn(error.message);
18820
18531
  media.currentTime = targetTime;
18821
18532
  hls.trigger(Events.ERROR, {
18822
18533
  type: ErrorTypes.MEDIA_ERROR,
@@ -18826,7 +18537,7 @@ class GapController extends Logger {
18826
18537
  });
18827
18538
  } else {
18828
18539
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
18829
- this.error(error.message);
18540
+ logger.error(error.message);
18830
18541
  hls.trigger(Events.ERROR, {
18831
18542
  type: ErrorTypes.MEDIA_ERROR,
18832
18543
  details: ErrorDetails.BUFFER_STALLED_ERROR,
@@ -18841,7 +18552,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
18841
18552
 
18842
18553
  class StreamController extends BaseStreamController {
18843
18554
  constructor(hls, fragmentTracker, keyLoader) {
18844
- super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
18555
+ super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
18845
18556
  this.audioCodecSwap = false;
18846
18557
  this.gapController = null;
18847
18558
  this.level = -1;
@@ -18849,43 +18560,27 @@ class StreamController extends BaseStreamController {
18849
18560
  this.altAudio = false;
18850
18561
  this.audioOnly = false;
18851
18562
  this.fragPlaying = null;
18563
+ this.onvplaying = null;
18564
+ this.onvseeked = null;
18852
18565
  this.fragLastKbps = 0;
18853
18566
  this.couldBacktrack = false;
18854
18567
  this.backtrackFragment = null;
18855
18568
  this.audioCodecSwitch = false;
18856
18569
  this.videoBuffer = null;
18857
- this.onMediaPlaying = () => {
18858
- // tick to speed up FRAG_CHANGED triggering
18859
- this.tick();
18860
- };
18861
- this.onMediaSeeked = () => {
18862
- const media = this.media;
18863
- const currentTime = media ? media.currentTime : null;
18864
- if (isFiniteNumber(currentTime)) {
18865
- this.log(`Media seeked to ${currentTime.toFixed(3)}`);
18866
- }
18867
-
18868
- // If seeked was issued before buffer was appended do not tick immediately
18869
- const bufferInfo = this.getMainFwdBufferInfo();
18870
- if (bufferInfo === null || bufferInfo.len === 0) {
18871
- this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
18872
- return;
18873
- }
18874
-
18875
- // tick to speed up FRAG_CHANGED triggering
18876
- this.tick();
18877
- };
18878
- this.registerListeners();
18570
+ this._registerListeners();
18879
18571
  }
18880
- registerListeners() {
18881
- super.registerListeners();
18572
+ _registerListeners() {
18882
18573
  const {
18883
18574
  hls
18884
18575
  } = this;
18576
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
18577
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
18578
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18885
18579
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18886
18580
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
18887
18581
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
18888
18582
  hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
18583
+ hls.on(Events.ERROR, this.onError, this);
18889
18584
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
18890
18585
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
18891
18586
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -18893,14 +18588,17 @@ class StreamController extends BaseStreamController {
18893
18588
  hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
18894
18589
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
18895
18590
  }
18896
- unregisterListeners() {
18897
- super.unregisterListeners();
18591
+ _unregisterListeners() {
18898
18592
  const {
18899
18593
  hls
18900
18594
  } = this;
18595
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
18596
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
18597
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18901
18598
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18902
18599
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
18903
18600
  hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
18601
+ hls.off(Events.ERROR, this.onError, this);
18904
18602
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
18905
18603
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
18906
18604
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -18909,9 +18607,7 @@ class StreamController extends BaseStreamController {
18909
18607
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
18910
18608
  }
18911
18609
  onHandlerDestroying() {
18912
- // @ts-ignore
18913
- this.onMediaPlaying = this.onMediaSeeked = null;
18914
- this.unregisterListeners();
18610
+ this._unregisterListeners();
18915
18611
  super.onHandlerDestroying();
18916
18612
  }
18917
18613
  startLoad(startPosition) {
@@ -19015,15 +18711,11 @@ class StreamController extends BaseStreamController {
19015
18711
  levels,
19016
18712
  media
19017
18713
  } = this;
19018
- const {
19019
- config,
19020
- nextLoadLevel: level
19021
- } = hls;
19022
18714
 
19023
18715
  // if start level not parsed yet OR
19024
18716
  // if video not attached AND start fragment already requested OR start frag prefetch not enabled
19025
18717
  // exit loop, as we either need more info (level not parsed) or we need media to be attached to load new fragment
19026
- if (levelLastLoaded === null || !media && (this.startFragRequested || !config.startFragPrefetch)) {
18718
+ if (levelLastLoaded === null || !media && (this.startFragRequested || !hls.config.startFragPrefetch)) {
19027
18719
  return;
19028
18720
  }
19029
18721
 
@@ -19031,7 +18723,8 @@ class StreamController extends BaseStreamController {
19031
18723
  if (this.altAudio && this.audioOnly) {
19032
18724
  return;
19033
18725
  }
19034
- if (!this.buffering || !(levels != null && levels[level])) {
18726
+ const level = hls.nextLoadLevel;
18727
+ if (!(levels != null && levels[level])) {
19035
18728
  return;
19036
18729
  }
19037
18730
  const levelInfo = levels[level];
@@ -19239,17 +18932,20 @@ class StreamController extends BaseStreamController {
19239
18932
  onMediaAttached(event, data) {
19240
18933
  super.onMediaAttached(event, data);
19241
18934
  const media = data.media;
19242
- media.addEventListener('playing', this.onMediaPlaying);
19243
- media.addEventListener('seeked', this.onMediaSeeked);
18935
+ this.onvplaying = this.onMediaPlaying.bind(this);
18936
+ this.onvseeked = this.onMediaSeeked.bind(this);
18937
+ media.addEventListener('playing', this.onvplaying);
18938
+ media.addEventListener('seeked', this.onvseeked);
19244
18939
  this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
19245
18940
  }
19246
18941
  onMediaDetaching() {
19247
18942
  const {
19248
18943
  media
19249
18944
  } = this;
19250
- if (media) {
19251
- media.removeEventListener('playing', this.onMediaPlaying);
19252
- media.removeEventListener('seeked', this.onMediaSeeked);
18945
+ if (media && this.onvplaying && this.onvseeked) {
18946
+ media.removeEventListener('playing', this.onvplaying);
18947
+ media.removeEventListener('seeked', this.onvseeked);
18948
+ this.onvplaying = this.onvseeked = null;
19253
18949
  this.videoBuffer = null;
19254
18950
  }
19255
18951
  this.fragPlaying = null;
@@ -19259,6 +18955,27 @@ class StreamController extends BaseStreamController {
19259
18955
  }
19260
18956
  super.onMediaDetaching();
19261
18957
  }
18958
+ onMediaPlaying() {
18959
+ // tick to speed up FRAG_CHANGED triggering
18960
+ this.tick();
18961
+ }
18962
+ onMediaSeeked() {
18963
+ const media = this.media;
18964
+ const currentTime = media ? media.currentTime : null;
18965
+ if (isFiniteNumber(currentTime)) {
18966
+ this.log(`Media seeked to ${currentTime.toFixed(3)}`);
18967
+ }
18968
+
18969
+ // If seeked was issued before buffer was appended do not tick immediately
18970
+ const bufferInfo = this.getMainFwdBufferInfo();
18971
+ if (bufferInfo === null || bufferInfo.len === 0) {
18972
+ this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
18973
+ return;
18974
+ }
18975
+
18976
+ // tick to speed up FRAG_CHANGED triggering
18977
+ this.tick();
18978
+ }
19262
18979
  onManifestLoading() {
19263
18980
  // reset buffer on manifest loading
19264
18981
  this.log('Trigger BUFFER_RESET');
@@ -19550,10 +19267,8 @@ class StreamController extends BaseStreamController {
19550
19267
  }
19551
19268
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
19552
19269
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
19553
- const state = this.state;
19554
- const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
19555
- const levelDetails = this.getLevelDetails();
19556
- gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
19270
+ const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
19271
+ gapController.poll(this.lastCurrentTime, activeFrag);
19557
19272
  }
19558
19273
  this.lastCurrentTime = media.currentTime;
19559
19274
  }
@@ -19991,7 +19706,7 @@ class Hls {
19991
19706
  * Get the video-dev/hls.js package version.
19992
19707
  */
19993
19708
  static get version() {
19994
- return "1.5.6-0.canary.9999";
19709
+ return "1.5.6";
19995
19710
  }
19996
19711
 
19997
19712
  /**
@@ -20054,12 +19769,9 @@ class Hls {
20054
19769
  * The configuration object provided on player instantiation.
20055
19770
  */
20056
19771
  this.userConfig = void 0;
20057
- /**
20058
- * The logger functions used by this player instance, configured on player instantiation.
20059
- */
20060
- this.logger = void 0;
20061
19772
  this.coreComponents = void 0;
20062
19773
  this.networkControllers = void 0;
19774
+ this.started = false;
20063
19775
  this._emitter = new EventEmitter();
20064
19776
  this._autoLevelCapping = -1;
20065
19777
  this._maxHdcpLevel = null;
@@ -20076,11 +19788,11 @@ class Hls {
20076
19788
  this._media = null;
20077
19789
  this.url = null;
20078
19790
  this.triggeringException = void 0;
20079
- const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
20080
- const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
19791
+ enableLogs(userConfig.debug || false, 'Hls instance');
19792
+ const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
20081
19793
  this.userConfig = userConfig;
20082
19794
  if (config.progressive) {
20083
- enableStreamingMode(config, logger);
19795
+ enableStreamingMode(config);
20084
19796
  }
20085
19797
 
20086
19798
  // core controllers and network loaders
@@ -20179,7 +19891,7 @@ class Hls {
20179
19891
  try {
20180
19892
  return this.emit(event, event, eventObject);
20181
19893
  } catch (error) {
20182
- this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
19894
+ logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
20183
19895
  // Prevent recursion in error event handlers that throw #5497
20184
19896
  if (!this.triggeringException) {
20185
19897
  this.triggeringException = true;
@@ -20205,7 +19917,7 @@ class Hls {
20205
19917
  * Dispose of the instance
20206
19918
  */
20207
19919
  destroy() {
20208
- this.logger.log('destroy');
19920
+ logger.log('destroy');
20209
19921
  this.trigger(Events.DESTROYING, undefined);
20210
19922
  this.detachMedia();
20211
19923
  this.removeAllListeners();
@@ -20226,7 +19938,7 @@ class Hls {
20226
19938
  * Attaches Hls.js to a media element
20227
19939
  */
20228
19940
  attachMedia(media) {
20229
- this.logger.log('attachMedia');
19941
+ logger.log('attachMedia');
20230
19942
  this._media = media;
20231
19943
  this.trigger(Events.MEDIA_ATTACHING, {
20232
19944
  media: media
@@ -20237,7 +19949,7 @@ class Hls {
20237
19949
  * Detach Hls.js from the media
20238
19950
  */
20239
19951
  detachMedia() {
20240
- this.logger.log('detachMedia');
19952
+ logger.log('detachMedia');
20241
19953
  this.trigger(Events.MEDIA_DETACHING, undefined);
20242
19954
  this._media = null;
20243
19955
  }
@@ -20254,7 +19966,7 @@ class Hls {
20254
19966
  });
20255
19967
  this._autoLevelCapping = -1;
20256
19968
  this._maxHdcpLevel = null;
20257
- this.logger.log(`loadSource:${loadingSource}`);
19969
+ logger.log(`loadSource:${loadingSource}`);
20258
19970
  if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
20259
19971
  this.detachMedia();
20260
19972
  this.attachMedia(media);
@@ -20273,7 +19985,8 @@ class Hls {
20273
19985
  * Defaults to -1 (None: starts from earliest point)
20274
19986
  */
20275
19987
  startLoad(startPosition = -1) {
20276
- this.logger.log(`startLoad(${startPosition})`);
19988
+ logger.log(`startLoad(${startPosition})`);
19989
+ this.started = true;
20277
19990
  this.networkControllers.forEach(controller => {
20278
19991
  controller.startLoad(startPosition);
20279
19992
  });
@@ -20283,31 +19996,34 @@ class Hls {
20283
19996
  * Stop loading of any stream data.
20284
19997
  */
20285
19998
  stopLoad() {
20286
- this.logger.log('stopLoad');
19999
+ logger.log('stopLoad');
20000
+ this.started = false;
20287
20001
  this.networkControllers.forEach(controller => {
20288
20002
  controller.stopLoad();
20289
20003
  });
20290
20004
  }
20291
20005
 
20292
20006
  /**
20293
- * Resumes stream controller segment loading after `pauseBuffering` has been called.
20007
+ * Resumes stream controller segment loading if previously started.
20294
20008
  */
20295
20009
  resumeBuffering() {
20296
- this.networkControllers.forEach(controller => {
20297
- if (controller.resumeBuffering) {
20298
- controller.resumeBuffering();
20299
- }
20300
- });
20010
+ if (this.started) {
20011
+ this.networkControllers.forEach(controller => {
20012
+ if ('fragmentLoader' in controller) {
20013
+ controller.startLoad(-1);
20014
+ }
20015
+ });
20016
+ }
20301
20017
  }
20302
20018
 
20303
20019
  /**
20304
- * Prevents stream controller from loading new segments until `resumeBuffering` is called.
20020
+ * Stops stream controller segment loading without changing 'started' state like stopLoad().
20305
20021
  * This allows for media buffering to be paused without interupting playlist loading.
20306
20022
  */
20307
20023
  pauseBuffering() {
20308
20024
  this.networkControllers.forEach(controller => {
20309
- if (controller.pauseBuffering) {
20310
- controller.pauseBuffering();
20025
+ if ('fragmentLoader' in controller) {
20026
+ controller.stopLoad();
20311
20027
  }
20312
20028
  });
20313
20029
  }
@@ -20316,7 +20032,7 @@ class Hls {
20316
20032
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
20317
20033
  */
20318
20034
  swapAudioCodec() {
20319
- this.logger.log('swapAudioCodec');
20035
+ logger.log('swapAudioCodec');
20320
20036
  this.streamController.swapAudioCodec();
20321
20037
  }
20322
20038
 
@@ -20327,7 +20043,7 @@ class Hls {
20327
20043
  * Automatic recovery of media-errors by this process is configurable.
20328
20044
  */
20329
20045
  recoverMediaError() {
20330
- this.logger.log('recoverMediaError');
20046
+ logger.log('recoverMediaError');
20331
20047
  const media = this._media;
20332
20048
  this.detachMedia();
20333
20049
  if (media) {
@@ -20357,7 +20073,7 @@ class Hls {
20357
20073
  * 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.
20358
20074
  */
20359
20075
  set currentLevel(newLevel) {
20360
- this.logger.log(`set currentLevel:${newLevel}`);
20076
+ logger.log(`set currentLevel:${newLevel}`);
20361
20077
  this.levelController.manualLevel = newLevel;
20362
20078
  this.streamController.immediateLevelSwitch();
20363
20079
  }
@@ -20376,7 +20092,7 @@ class Hls {
20376
20092
  * @param newLevel - Pass -1 for automatic level selection
20377
20093
  */
20378
20094
  set nextLevel(newLevel) {
20379
- this.logger.log(`set nextLevel:${newLevel}`);
20095
+ logger.log(`set nextLevel:${newLevel}`);
20380
20096
  this.levelController.manualLevel = newLevel;
20381
20097
  this.streamController.nextLevelSwitch();
20382
20098
  }
@@ -20395,7 +20111,7 @@ class Hls {
20395
20111
  * @param newLevel - Pass -1 for automatic level selection
20396
20112
  */
20397
20113
  set loadLevel(newLevel) {
20398
- this.logger.log(`set loadLevel:${newLevel}`);
20114
+ logger.log(`set loadLevel:${newLevel}`);
20399
20115
  this.levelController.manualLevel = newLevel;
20400
20116
  }
20401
20117
 
@@ -20426,7 +20142,7 @@ class Hls {
20426
20142
  * Sets "first-level", see getter.
20427
20143
  */
20428
20144
  set firstLevel(newLevel) {
20429
- this.logger.log(`set firstLevel:${newLevel}`);
20145
+ logger.log(`set firstLevel:${newLevel}`);
20430
20146
  this.levelController.firstLevel = newLevel;
20431
20147
  }
20432
20148
 
@@ -20451,7 +20167,7 @@ class Hls {
20451
20167
  * (determined from download of first segment)
20452
20168
  */
20453
20169
  set startLevel(newLevel) {
20454
- this.logger.log(`set startLevel:${newLevel}`);
20170
+ logger.log(`set startLevel:${newLevel}`);
20455
20171
  // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
20456
20172
  if (newLevel !== -1) {
20457
20173
  newLevel = Math.max(newLevel, this.minAutoLevel);
@@ -20526,7 +20242,7 @@ class Hls {
20526
20242
  */
20527
20243
  set autoLevelCapping(newLevel) {
20528
20244
  if (this._autoLevelCapping !== newLevel) {
20529
- this.logger.log(`set autoLevelCapping:${newLevel}`);
20245
+ logger.log(`set autoLevelCapping:${newLevel}`);
20530
20246
  this._autoLevelCapping = newLevel;
20531
20247
  this.levelController.checkMaxAutoUpdated();
20532
20248
  }
@@ -20805,5 +20521,5 @@ var KeySystemFormats = empty.KeySystemFormats;
20805
20521
  var KeySystems = empty.KeySystems;
20806
20522
  var SubtitleStreamController = empty.SubtitleStreamController;
20807
20523
  var TimelineController = empty.TimelineController;
20808
- 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 };
20524
+ 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 };
20809
20525
  //# sourceMappingURL=hls.light.mjs.map