hls.js 1.5.12-0.canary.10366 → 1.5.12-0.canary.10367

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.
@@ -420,7 +420,7 @@ function enableLogs(debugConfig, context, id) {
420
420
  // Some browsers don't allow to use bind on console object anyway
421
421
  // fallback to default if needed
422
422
  try {
423
- newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.12-0.canary.10366"}`);
423
+ newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.12-0.canary.10367"}`);
424
424
  } catch (e) {
425
425
  /* log fn threw an exception. All logger methods are no-ops. */
426
426
  return createLogger();
@@ -3535,10 +3535,9 @@ class PlaylistLoader {
3535
3535
  type
3536
3536
  } = context;
3537
3537
  const url = getResponseUrl(response, context);
3538
- const levelUrlId = 0;
3539
3538
  const levelId = isFiniteNumber(level) ? level : isFiniteNumber(id) ? id : 0;
3540
3539
  const levelType = mapContextToLevelType(context);
3541
- const levelDetails = M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, levelUrlId, this.variableList);
3540
+ const levelDetails = M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, 0, this.variableList);
3542
3541
 
3543
3542
  // We have done our first request (Manifest-type) and receive
3544
3543
  // not a master playlist but a chunk-list (track/level)
@@ -5284,8 +5283,7 @@ function computeReloadInterval(newDetails, distanceToLiveEdgeMs = Infinity) {
5284
5283
  }
5285
5284
  return Math.round(reloadInterval);
5286
5285
  }
5287
- function getFragmentWithSN(level, sn, fragCurrent) {
5288
- const details = level == null ? void 0 : level.details;
5286
+ function getFragmentWithSN(details, sn, fragCurrent) {
5289
5287
  if (!details) {
5290
5288
  return null;
5291
5289
  }
@@ -5302,12 +5300,11 @@ function getFragmentWithSN(level, sn, fragCurrent) {
5302
5300
  }
5303
5301
  return null;
5304
5302
  }
5305
- function getPartWith(level, sn, partIndex) {
5306
- var _level$details;
5307
- if (!(level != null && level.details)) {
5303
+ function getPartWith(details, sn, partIndex) {
5304
+ if (!details) {
5308
5305
  return null;
5309
5306
  }
5310
- return findPart((_level$details = level.details) == null ? void 0 : _level$details.partList, sn, partIndex);
5307
+ return findPart(details.partList, sn, partIndex);
5311
5308
  }
5312
5309
  function findPart(partList, sn, partIndex) {
5313
5310
  if (partList) {
@@ -5451,12 +5448,15 @@ function findFragmentByPDT(fragments, PDTValue, maxFragLookUpTolerance) {
5451
5448
  function findFragmentByPTS(fragPrevious, fragments, bufferEnd = 0, maxFragLookUpTolerance = 0, nextFragLookupTolerance = 0.005) {
5452
5449
  let fragNext = null;
5453
5450
  if (fragPrevious) {
5454
- fragNext = fragments[fragPrevious.sn - fragments[0].sn + 1] || null;
5451
+ fragNext = fragments[1 + fragPrevious.sn - fragments[0].sn] || null;
5455
5452
  // check for buffer-end rounding error
5456
5453
  const bufferEdgeError = fragPrevious.endDTS - bufferEnd;
5457
5454
  if (bufferEdgeError > 0 && bufferEdgeError < 0.0000015) {
5458
5455
  bufferEnd += 0.0000015;
5459
5456
  }
5457
+ if (fragNext && fragPrevious.level !== fragNext.level && fragNext.end <= fragPrevious.end) {
5458
+ fragNext = fragments[2 + fragPrevious.sn - fragments[0].sn] || null;
5459
+ }
5460
5460
  } else if (bufferEnd === 0 && fragments[0].start === 0) {
5461
5461
  fragNext = fragments[0];
5462
5462
  }
@@ -5608,7 +5608,7 @@ class ErrorController extends Logger {
5608
5608
  this.playlistError = 0;
5609
5609
  }
5610
5610
  onError(event, data) {
5611
- var _data$frag, _data$level;
5611
+ var _data$frag;
5612
5612
  if (data.fatal) {
5613
5613
  return;
5614
5614
  }
@@ -5624,10 +5624,7 @@ class ErrorController extends Logger {
5624
5624
  case ErrorDetails.FRAG_PARSING_ERROR:
5625
5625
  // ignore empty segment errors marked as gap
5626
5626
  if ((_data$frag = data.frag) != null && _data$frag.gap) {
5627
- data.errorAction = {
5628
- action: NetworkErrorAction.DoNothing,
5629
- flags: ErrorActionFlags.None
5630
- };
5627
+ data.errorAction = createDoNothingErrorAction();
5631
5628
  return;
5632
5629
  }
5633
5630
  // falls through
@@ -5694,7 +5691,11 @@ class ErrorController extends Logger {
5694
5691
  case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
5695
5692
  case ErrorDetails.REMUX_ALLOC_ERROR:
5696
5693
  case ErrorDetails.BUFFER_APPEND_ERROR:
5697
- data.errorAction = this.getLevelSwitchAction(data, (_data$level = data.level) != null ? _data$level : hls.loadLevel);
5694
+ // Buffer-controller can set errorAction when append errors can be ignored or resolved locally
5695
+ if (!data.errorAction) {
5696
+ var _data$level;
5697
+ data.errorAction = this.getLevelSwitchAction(data, (_data$level = data.level) != null ? _data$level : hls.loadLevel);
5698
+ }
5698
5699
  return;
5699
5700
  case ErrorDetails.INTERNAL_EXCEPTION:
5700
5701
  case ErrorDetails.BUFFER_APPENDING_ERROR:
@@ -5703,10 +5704,7 @@ class ErrorController extends Logger {
5703
5704
  case ErrorDetails.BUFFER_STALLED_ERROR:
5704
5705
  case ErrorDetails.BUFFER_SEEK_OVER_HOLE:
5705
5706
  case ErrorDetails.BUFFER_NUDGE_ON_STALL:
5706
- data.errorAction = {
5707
- action: NetworkErrorAction.DoNothing,
5708
- flags: ErrorActionFlags.None
5709
- };
5707
+ data.errorAction = createDoNothingErrorAction();
5710
5708
  return;
5711
5709
  }
5712
5710
  if (data.type === ErrorTypes.KEY_SYSTEM_ERROR) {
@@ -5919,6 +5917,13 @@ class ErrorController extends Logger {
5919
5917
  }
5920
5918
  }
5921
5919
  }
5920
+ function createDoNothingErrorAction(resolved) {
5921
+ const errorAction = {
5922
+ action: NetworkErrorAction.DoNothing,
5923
+ flags: ErrorActionFlags.None
5924
+ };
5925
+ return errorAction;
5926
+ }
5922
5927
 
5923
5928
  class BasePlaylistController extends Logger {
5924
5929
  constructor(hls, logPrefix) {
@@ -6371,6 +6376,7 @@ function getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPrefer
6371
6376
  if (!hasCurrentVideoRange) {
6372
6377
  currentVideoRange = undefined;
6373
6378
  }
6379
+ const hasMultipleSets = codecSets.length > 1;
6374
6380
  const codecSet = codecSets.reduce((selected, candidate) => {
6375
6381
  // Remove candiates which do not meet bitrate, default audio, stereo or channels preference, 1080p or lower, 30fps or lower, or SDR/HDR selection if present
6376
6382
  const candidateTier = codecTiers[candidate];
@@ -6378,46 +6384,48 @@ function getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPrefer
6378
6384
  return selected;
6379
6385
  }
6380
6386
  videoRanges = hasCurrentVideoRange ? allowedVideoRanges.filter(range => candidateTier.videoRanges[range] > 0) : [];
6381
- if (candidateTier.minBitrate > currentBw) {
6382
- logStartCodecCandidateIgnored(candidate, `min bitrate of ${candidateTier.minBitrate} > current estimate of ${currentBw}`);
6383
- return selected;
6384
- }
6385
- if (!candidateTier.hasDefaultAudio) {
6386
- logStartCodecCandidateIgnored(candidate, `no renditions with default or auto-select sound found`);
6387
- return selected;
6388
- }
6389
- if (audioCodecPreference && candidate.indexOf(audioCodecPreference.substring(0, 4)) % 5 !== 0) {
6390
- logStartCodecCandidateIgnored(candidate, `audio codec preference "${audioCodecPreference}" not found`);
6391
- return selected;
6392
- }
6393
- if (channelsPreference && !preferStereo) {
6394
- if (!candidateTier.channels[channelsPreference]) {
6395
- logStartCodecCandidateIgnored(candidate, `no renditions with ${channelsPreference} channel sound found (channels options: ${Object.keys(candidateTier.channels)})`);
6387
+ if (hasMultipleSets) {
6388
+ if (candidateTier.minBitrate > currentBw) {
6389
+ logStartCodecCandidateIgnored(candidate, `min bitrate of ${candidateTier.minBitrate} > current estimate of ${currentBw}`);
6390
+ return selected;
6391
+ }
6392
+ if (!candidateTier.hasDefaultAudio) {
6393
+ logStartCodecCandidateIgnored(candidate, `no renditions with default or auto-select sound found`);
6394
+ return selected;
6395
+ }
6396
+ if (audioCodecPreference && candidate.indexOf(audioCodecPreference.substring(0, 4)) % 5 !== 0) {
6397
+ logStartCodecCandidateIgnored(candidate, `audio codec preference "${audioCodecPreference}" not found`);
6398
+ return selected;
6399
+ }
6400
+ if (channelsPreference && !preferStereo) {
6401
+ if (!candidateTier.channels[channelsPreference]) {
6402
+ logStartCodecCandidateIgnored(candidate, `no renditions with ${channelsPreference} channel sound found (channels options: ${Object.keys(candidateTier.channels)})`);
6403
+ return selected;
6404
+ }
6405
+ } else if ((!audioCodecPreference || preferStereo) && hasStereo && candidateTier.channels['2'] === 0) {
6406
+ logStartCodecCandidateIgnored(candidate, `no renditions with stereo sound found`);
6407
+ return selected;
6408
+ }
6409
+ if (candidateTier.minHeight > maxHeight) {
6410
+ logStartCodecCandidateIgnored(candidate, `min resolution of ${candidateTier.minHeight} > maximum of ${maxHeight}`);
6411
+ return selected;
6412
+ }
6413
+ if (candidateTier.minFramerate > maxFramerate) {
6414
+ logStartCodecCandidateIgnored(candidate, `min framerate of ${candidateTier.minFramerate} > maximum of ${maxFramerate}`);
6415
+ return selected;
6416
+ }
6417
+ if (!videoRanges.some(range => candidateTier.videoRanges[range] > 0)) {
6418
+ logStartCodecCandidateIgnored(candidate, `no variants with VIDEO-RANGE of ${JSON.stringify(videoRanges)} found`);
6419
+ return selected;
6420
+ }
6421
+ if (videoCodecPreference && candidate.indexOf(videoCodecPreference.substring(0, 4)) % 5 !== 0) {
6422
+ logStartCodecCandidateIgnored(candidate, `video codec preference "${videoCodecPreference}" not found`);
6423
+ return selected;
6424
+ }
6425
+ if (candidateTier.maxScore < selectedScore) {
6426
+ logStartCodecCandidateIgnored(candidate, `max score of ${candidateTier.maxScore} < selected max of ${selectedScore}`);
6396
6427
  return selected;
6397
6428
  }
6398
- } else if ((!audioCodecPreference || preferStereo) && hasStereo && candidateTier.channels['2'] === 0) {
6399
- logStartCodecCandidateIgnored(candidate, `no renditions with stereo sound found`);
6400
- return selected;
6401
- }
6402
- if (candidateTier.minHeight > maxHeight) {
6403
- logStartCodecCandidateIgnored(candidate, `min resolution of ${candidateTier.minHeight} > maximum of ${maxHeight}`);
6404
- return selected;
6405
- }
6406
- if (candidateTier.minFramerate > maxFramerate) {
6407
- logStartCodecCandidateIgnored(candidate, `min framerate of ${candidateTier.minFramerate} > maximum of ${maxFramerate}`);
6408
- return selected;
6409
- }
6410
- if (!videoRanges.some(range => candidateTier.videoRanges[range] > 0)) {
6411
- logStartCodecCandidateIgnored(candidate, `no variants with VIDEO-RANGE of ${JSON.stringify(videoRanges)} found`);
6412
- return selected;
6413
- }
6414
- if (videoCodecPreference && candidate.indexOf(videoCodecPreference.substring(0, 4)) % 5 !== 0) {
6415
- logStartCodecCandidateIgnored(candidate, `video codec preference "${videoCodecPreference}" not found`);
6416
- return selected;
6417
- }
6418
- if (candidateTier.maxScore < selectedScore) {
6419
- logStartCodecCandidateIgnored(candidate, `max score of ${candidateTier.maxScore} < selected max of ${selectedScore}`);
6420
- return selected;
6421
6429
  }
6422
6430
  // Remove candiates with less preferred codecs or more errors
6423
6431
  if (selected && (codecsSetSelectionPreferenceValue(candidate) >= codecsSetSelectionPreferenceValue(selected) || candidateTier.fragmentError > codecTiers[selected].fragmentError)) {
@@ -7095,7 +7103,7 @@ class AbrController extends Logger {
7095
7103
  if (levelsSkipped.length) {
7096
7104
  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}`);
7097
7105
  }
7098
- 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:${level.codecSet} videoRange:${level.videoRange} hls.loadLevel:${loadLevel}`);
7106
+ 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:${levelInfo.codecSet} videoRange:${levelInfo.videoRange} hls.loadLevel:${loadLevel}`);
7099
7107
  }
7100
7108
  if (firstSelection) {
7101
7109
  this.firstSelection = i;
@@ -7437,6 +7445,7 @@ class BufferController extends Logger {
7437
7445
  hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
7438
7446
  hls.on(Events.FRAG_PARSED, this.onFragParsed, this);
7439
7447
  hls.on(Events.FRAG_CHANGED, this.onFragChanged, this);
7448
+ hls.on(Events.ERROR, this.onError, this);
7440
7449
  }
7441
7450
  unregisterListeners() {
7442
7451
  const {
@@ -7454,6 +7463,7 @@ class BufferController extends Logger {
7454
7463
  hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
7455
7464
  hls.off(Events.FRAG_PARSED, this.onFragParsed, this);
7456
7465
  hls.off(Events.FRAG_CHANGED, this.onFragChanged, this);
7466
+ hls.off(Events.ERROR, this.onError, this);
7457
7467
  }
7458
7468
  _initSourceBuffer() {
7459
7469
  this.sourceBuffer = {};
@@ -7463,11 +7473,7 @@ class BufferController extends Logger {
7463
7473
  video: [],
7464
7474
  audiovideo: []
7465
7475
  };
7466
- this.appendErrors = {
7467
- audio: 0,
7468
- video: 0,
7469
- audiovideo: 0
7470
- };
7476
+ this.resetAppendErrors();
7471
7477
  this.lastMpegAudioChunk = null;
7472
7478
  this.blockedAudioAppend = null;
7473
7479
  this.lastVideoAppendEnd = 0;
@@ -8004,6 +8010,22 @@ class BufferController extends Logger {
8004
8010
  this.updateMediaSource(durationAndRange);
8005
8011
  }
8006
8012
  }
8013
+ onError(event, data) {
8014
+ if (data.details === ErrorDetails.BUFFER_APPEND_ERROR && data.frag) {
8015
+ var _data$errorAction;
8016
+ const nextAutoLevel = (_data$errorAction = data.errorAction) == null ? void 0 : _data$errorAction.nextAutoLevel;
8017
+ if (isFiniteNumber(nextAutoLevel) && nextAutoLevel !== data.frag.level) {
8018
+ this.resetAppendErrors();
8019
+ }
8020
+ }
8021
+ }
8022
+ resetAppendErrors() {
8023
+ this.appendErrors = {
8024
+ audio: 0,
8025
+ video: 0,
8026
+ audiovideo: 0
8027
+ };
8028
+ }
8007
8029
  trimBuffers() {
8008
8030
  const {
8009
8031
  hls,
@@ -10672,6 +10694,7 @@ class FragmentTracker {
10672
10694
  const {
10673
10695
  hls
10674
10696
  } = this;
10697
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
10675
10698
  hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
10676
10699
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
10677
10700
  hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
@@ -10680,6 +10703,7 @@ class FragmentTracker {
10680
10703
  const {
10681
10704
  hls
10682
10705
  } = this;
10706
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
10683
10707
  hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
10684
10708
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
10685
10709
  hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
@@ -10745,7 +10769,7 @@ class FragmentTracker {
10745
10769
  * The browser will unload parts of the buffer to free up memory for new buffer data
10746
10770
  * Fragments will need to be reloaded when the buffer is freed up, removing partial fragments will allow them to reload(since there might be parts that are still playable)
10747
10771
  */
10748
- detectEvictedFragments(elementaryStream, timeRange, playlistType, appendedPart) {
10772
+ detectEvictedFragments(elementaryStream, timeRange, playlistType, appendedPart, removeAppending) {
10749
10773
  if (this.timeRanges) {
10750
10774
  this.timeRanges[elementaryStream] = timeRange;
10751
10775
  }
@@ -10760,7 +10784,7 @@ class FragmentTracker {
10760
10784
  if (appendedPartSn >= fragmentEntity.body.sn) {
10761
10785
  return;
10762
10786
  }
10763
- if (!fragmentEntity.buffered && !fragmentEntity.loaded) {
10787
+ if (!fragmentEntity.buffered && (!fragmentEntity.loaded || removeAppending)) {
10764
10788
  if (fragmentEntity.body.type === playlistType) {
10765
10789
  this.removeFragment(fragmentEntity.body);
10766
10790
  }
@@ -10770,6 +10794,10 @@ class FragmentTracker {
10770
10794
  if (!esData) {
10771
10795
  return;
10772
10796
  }
10797
+ if (esData.time.length === 0) {
10798
+ this.removeFragment(fragmentEntity.body);
10799
+ return;
10800
+ }
10773
10801
  esData.time.some(time => {
10774
10802
  const isNotBuffered = !this.isTimeBuffered(time.startPTS, time.endPTS, timeRange);
10775
10803
  if (isNotBuffered) {
@@ -10956,6 +10984,9 @@ class FragmentTracker {
10956
10984
  }
10957
10985
  return false;
10958
10986
  }
10987
+ onManifestLoading() {
10988
+ this.removeAllFragments();
10989
+ }
10959
10990
  onFragLoaded(event, data) {
10960
10991
  // don't track initsegment (for which sn is not a number)
10961
10992
  // don't track frags used for bitrateTest, they're irrelevant.
@@ -11004,6 +11035,22 @@ class FragmentTracker {
11004
11035
  const fragKey = getFragmentKey(fragment);
11005
11036
  return !!this.fragments[fragKey];
11006
11037
  }
11038
+ hasFragments(type) {
11039
+ const {
11040
+ fragments
11041
+ } = this;
11042
+ const keys = Object.keys(fragments);
11043
+ if (!type) {
11044
+ return keys.length > 0;
11045
+ }
11046
+ for (let i = keys.length; i--;) {
11047
+ const fragmentEntity = fragments[keys[i]];
11048
+ if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === type) {
11049
+ return true;
11050
+ }
11051
+ }
11052
+ return false;
11053
+ }
11007
11054
  hasParts(type) {
11008
11055
  var _this$activePartLists;
11009
11056
  return !!((_this$activePartLists = this.activePartLists[type]) != null && _this$activePartLists.length);
@@ -11105,8 +11152,9 @@ class FragmentLoader {
11105
11152
  frag.gap = false;
11106
11153
  }
11107
11154
  }
11108
- const loader = this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
11155
+ const loader = this.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
11109
11156
  const loaderContext = createLoaderContext(frag);
11157
+ frag.loader = loader;
11110
11158
  const loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default);
11111
11159
  const loaderConfig = {
11112
11160
  loadPolicy,
@@ -11199,8 +11247,9 @@ class FragmentLoader {
11199
11247
  reject(createGapLoadError(frag, part));
11200
11248
  return;
11201
11249
  }
11202
- const loader = this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
11250
+ const loader = this.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
11203
11251
  const loaderContext = createLoaderContext(frag, part);
11252
+ frag.loader = loader;
11204
11253
  // Should we define another load policy for parts?
11205
11254
  const loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default);
11206
11255
  const loaderConfig = {
@@ -12438,6 +12487,7 @@ class BaseStreamController extends TaskLoop {
12438
12487
  this.initPTS = [];
12439
12488
  this.buffering = true;
12440
12489
  this.loadingParts = false;
12490
+ this.loopSn = void 0;
12441
12491
  this.onMediaSeeking = () => {
12442
12492
  const {
12443
12493
  config,
@@ -12537,6 +12587,9 @@ class BaseStreamController extends TaskLoop {
12537
12587
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
12538
12588
  startLoad(startPosition) {}
12539
12589
  stopLoad() {
12590
+ if (this.state === State.STOPPED) {
12591
+ return;
12592
+ }
12540
12593
  this.fragmentLoader.abort();
12541
12594
  this.keyLoader.abort(this.playlistType);
12542
12595
  const frag = this.fragCurrent;
@@ -12610,15 +12663,20 @@ class BaseStreamController extends TaskLoop {
12610
12663
  this.keyLoader.detach();
12611
12664
  }
12612
12665
  this.media = this.mediaBuffer = null;
12613
- this.loadedmetadata = false;
12666
+ this.loopSn = undefined;
12667
+ this.startFragRequested = this.loadedmetadata = this.loadingParts = false;
12614
12668
  this.fragmentTracker.removeAllFragments();
12615
12669
  this.stopLoad();
12616
12670
  }
12617
- onManifestLoading() {}
12671
+ onManifestLoading() {
12672
+ this.initPTS = [];
12673
+ this.levels = this.levelLastLoaded = this.fragCurrent = null;
12674
+ this.lastCurrentTime = this.startPosition = 0;
12675
+ this.startFragRequested = false;
12676
+ }
12618
12677
  onError(event, data) {}
12619
12678
  onManifestLoaded(event, data) {
12620
12679
  this.startTimeOffset = data.startTimeOffset;
12621
- this.initPTS = [];
12622
12680
  }
12623
12681
  onHandlerDestroying() {
12624
12682
  this.stopLoad();
@@ -12641,6 +12699,7 @@ class BaseStreamController extends TaskLoop {
12641
12699
  super.onHandlerDestroyed();
12642
12700
  }
12643
12701
  loadFragment(frag, level, targetBufferTime) {
12702
+ this.startFragRequested = true;
12644
12703
  this._loadFragForPlayback(frag, level, targetBufferTime);
12645
12704
  }
12646
12705
  _loadFragForPlayback(frag, level, targetBufferTime) {
@@ -12869,6 +12928,7 @@ class BaseStreamController extends TaskLoop {
12869
12928
  _handleFragmentLoadProgress(frag) {}
12870
12929
  _doFragLoad(frag, level, targetBufferTime = null, progressCallback) {
12871
12930
  var _frag$decryptdata;
12931
+ this.fragCurrent = frag;
12872
12932
  const details = level == null ? void 0 : level.details;
12873
12933
  if (!this.levels || !details) {
12874
12934
  throw new Error(`frag load aborted, missing level${details ? '' : ' detail'}s`);
@@ -12996,7 +13056,7 @@ class BaseStreamController extends TaskLoop {
12996
13056
  partsLoaded[part.index] = partLoadedData;
12997
13057
  const loadedPart = partLoadedData.part;
12998
13058
  this.hls.trigger(Events.FRAG_LOADED, partLoadedData);
12999
- const nextPart = getPartWith(level, frag.sn, part.index + 1) || findPart(initialPartList, frag.sn, part.index + 1);
13059
+ const nextPart = getPartWith(level.details, frag.sn, part.index + 1) || findPart(initialPartList, frag.sn, part.index + 1);
13000
13060
  if (nextPart) {
13001
13061
  loadPart(nextPart);
13002
13062
  } else {
@@ -13091,8 +13151,9 @@ class BaseStreamController extends TaskLoop {
13091
13151
  return null;
13092
13152
  }
13093
13153
  const level = levels[levelIndex];
13094
- const part = partIndex > -1 ? getPartWith(level, sn, partIndex) : null;
13095
- const frag = part ? part.fragment : getFragmentWithSN(level, sn, fragCurrent);
13154
+ const levelDetails = level.details;
13155
+ const part = partIndex > -1 ? getPartWith(levelDetails, sn, partIndex) : null;
13156
+ const frag = part ? part.fragment : getFragmentWithSN(levelDetails, sn, fragCurrent);
13096
13157
  if (!frag) {
13097
13158
  return null;
13098
13159
  }
@@ -13207,7 +13268,8 @@ class BaseStreamController extends TaskLoop {
13207
13268
  return false;
13208
13269
  }
13209
13270
  getAppendedFrag(position, playlistType = PlaylistLevelType.MAIN) {
13210
- const fragOrPart = this.fragmentTracker.getAppendedFrag(position, PlaylistLevelType.MAIN);
13271
+ var _this$fragmentTracker;
13272
+ const fragOrPart = (_this$fragmentTracker = this.fragmentTracker) == null ? void 0 : _this$fragmentTracker.getAppendedFrag(position, playlistType);
13211
13273
  if (fragOrPart && 'fragment' in fragOrPart) {
13212
13274
  return fragOrPart.fragment;
13213
13275
  }
@@ -13262,22 +13324,25 @@ class BaseStreamController extends TaskLoop {
13262
13324
  return (trackerState === FragmentState.OK || trackerState === FragmentState.PARTIAL && !!frag.gap) && this.nextLoadPosition > targetBufferTime;
13263
13325
  }
13264
13326
  getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, playlistType, maxBufLen) {
13265
- const gapStart = frag.gap;
13266
- const nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails);
13267
- if (nextFragment === null) {
13268
- return nextFragment;
13269
- }
13270
- frag = nextFragment;
13271
- if (gapStart && frag && !frag.gap && bufferInfo.nextStart) {
13272
- // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length
13273
- const nextbufferInfo = this.getFwdBufferInfoAtPos(this.mediaBuffer ? this.mediaBuffer : this.media, bufferInfo.nextStart, playlistType);
13274
- if (nextbufferInfo !== null && bufferInfo.len + nextbufferInfo.len >= maxBufLen) {
13275
- // Returning here might result in not finding an audio and video candiate to skip to
13276
- this.log(`buffer full after gaps in "${playlistType}" playlist starting at sn: ${frag.sn}`);
13277
- return null;
13327
+ let nextFragment = null;
13328
+ if (frag.gap) {
13329
+ nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails);
13330
+ if (nextFragment && !nextFragment.gap && bufferInfo.nextStart) {
13331
+ // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length
13332
+ const nextbufferInfo = this.getFwdBufferInfoAtPos(this.mediaBuffer ? this.mediaBuffer : this.media, bufferInfo.nextStart, playlistType);
13333
+ if (nextbufferInfo !== null && bufferInfo.len + nextbufferInfo.len >= maxBufLen) {
13334
+ // Returning here might result in not finding an audio and video candiate to skip to
13335
+ const sn = nextFragment.sn;
13336
+ if (this.loopSn !== sn) {
13337
+ this.log(`buffer full after gaps in "${playlistType}" playlist starting at sn: ${sn}`);
13338
+ this.loopSn = sn;
13339
+ }
13340
+ return null;
13341
+ }
13278
13342
  }
13279
13343
  }
13280
- return frag;
13344
+ this.loopSn = undefined;
13345
+ return nextFragment;
13281
13346
  }
13282
13347
  mapToInitFragWhenRequired(frag) {
13283
13348
  // If an initSegment is present, it must be buffered first
@@ -13486,7 +13551,7 @@ class BaseStreamController extends TaskLoop {
13486
13551
  if (startTimeOffset !== null && isFiniteNumber(startTimeOffset)) {
13487
13552
  startPosition = sliding + startTimeOffset;
13488
13553
  if (startTimeOffset < 0) {
13489
- startPosition += details.totalduration;
13554
+ startPosition += details.edge;
13490
13555
  }
13491
13556
  startPosition = Math.min(Math.max(sliding, startPosition), sliding + details.totalduration);
13492
13557
  this.log(`Start time offset ${startTimeOffset} found in ${offsetInMultivariantPlaylist ? 'multivariant' : 'media'} playlist, adjust startPosition to ${startPosition}`);
@@ -17053,10 +17118,11 @@ class MP4Remuxer {
17053
17118
  if (this.ISGenerated) {
17054
17119
  var _videoTrack$pixelRati, _config$pixelRatio, _videoTrack$pixelRati2, _config$pixelRatio2;
17055
17120
  const config = this.videoTrackConfig;
17056
- if (config && (videoTrack.width !== config.width || videoTrack.height !== config.height || ((_videoTrack$pixelRati = videoTrack.pixelRatio) == null ? void 0 : _videoTrack$pixelRati[0]) !== ((_config$pixelRatio = config.pixelRatio) == null ? void 0 : _config$pixelRatio[0]) || ((_videoTrack$pixelRati2 = videoTrack.pixelRatio) == null ? void 0 : _videoTrack$pixelRati2[1]) !== ((_config$pixelRatio2 = config.pixelRatio) == null ? void 0 : _config$pixelRatio2[1]))) {
17121
+ if (config && (videoTrack.width !== config.width || videoTrack.height !== config.height || ((_videoTrack$pixelRati = videoTrack.pixelRatio) == null ? void 0 : _videoTrack$pixelRati[0]) !== ((_config$pixelRatio = config.pixelRatio) == null ? void 0 : _config$pixelRatio[0]) || ((_videoTrack$pixelRati2 = videoTrack.pixelRatio) == null ? void 0 : _videoTrack$pixelRati2[1]) !== ((_config$pixelRatio2 = config.pixelRatio) == null ? void 0 : _config$pixelRatio2[1])) || !config && enoughVideoSamples || this.nextAudioPts === null && enoughAudioSamples) {
17057
17122
  this.resetInitSegment();
17058
17123
  }
17059
- } else {
17124
+ }
17125
+ if (!this.ISGenerated) {
17060
17126
  initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);
17061
17127
  }
17062
17128
  const isVideoContiguous = this.isVideoContiguous;
@@ -17974,7 +18040,7 @@ class PassThroughRemuxer {
17974
18040
  if (isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) || initSegment.timescale !== initPTS.timescale && accurateTimeOffset) {
17975
18041
  initSegment.initPTS = decodeTime - timeOffset;
17976
18042
  if (initPTS && initPTS.timescale === 1) {
17977
- logger.warn(`Adjusting initPTS by ${initSegment.initPTS - initPTS.baseTime}`);
18043
+ logger.warn(`Adjusting initPTS @${timeOffset} from ${initPTS.baseTime / initPTS.timescale} to ${initSegment.initPTS}`);
17978
18044
  }
17979
18045
  this.initPTS = initPTS = {
17980
18046
  baseTime: initSegment.initPTS,
@@ -19693,7 +19759,6 @@ class StreamController extends BaseStreamController {
19693
19759
  loadFragment(frag, level, targetBufferTime) {
19694
19760
  // Check if fragment is not loaded
19695
19761
  const fragState = this.fragmentTracker.getState(frag);
19696
- this.fragCurrent = frag;
19697
19762
  if (fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) {
19698
19763
  if (frag.sn === 'initSegment') {
19699
19764
  this._loadInitSegment(frag, level);
@@ -19701,7 +19766,6 @@ class StreamController extends BaseStreamController {
19701
19766
  this.log(`Fragment ${frag.sn} of level ${frag.level} is being downloaded to test bitrate and will not be buffered`);
19702
19767
  this._loadBitrateTestFrag(frag, level);
19703
19768
  } else {
19704
- this.startFragRequested = true;
19705
19769
  super.loadFragment(frag, level, targetBufferTime);
19706
19770
  }
19707
19771
  } else {
@@ -19835,14 +19899,14 @@ class StreamController extends BaseStreamController {
19835
19899
  super.onMediaDetaching();
19836
19900
  }
19837
19901
  onManifestLoading() {
19902
+ super.onManifestLoading();
19838
19903
  // reset buffer on manifest loading
19839
19904
  this.log('Trigger BUFFER_RESET');
19840
19905
  this.hls.trigger(Events.BUFFER_RESET, undefined);
19841
- this.fragmentTracker.removeAllFragments();
19842
19906
  this.couldBacktrack = false;
19843
- this.startPosition = this.lastCurrentTime = this.fragLastKbps = 0;
19844
- this.levels = this.fragPlaying = this.backtrackFragment = this.levelLastLoaded = null;
19845
- this.altAudio = this.audioOnly = this.startFragRequested = false;
19907
+ this.fragLastKbps = 0;
19908
+ this.fragPlaying = this.backtrackFragment = null;
19909
+ this.altAudio = this.audioOnly = false;
19846
19910
  }
19847
19911
  onManifestParsed(event, data) {
19848
19912
  // detect if we have different kind of audio codecs used amongst playlists
@@ -20138,7 +20202,7 @@ class StreamController extends BaseStreamController {
20138
20202
  // in that case, reset startFragRequested flag
20139
20203
  if (!this.loadedmetadata) {
20140
20204
  this.startFragRequested = false;
20141
- this.nextLoadPosition = this.startPosition;
20205
+ this.nextLoadPosition = this.lastCurrentTime;
20142
20206
  }
20143
20207
  this.tickImmediate();
20144
20208
  }
@@ -20226,7 +20290,7 @@ class StreamController extends BaseStreamController {
20226
20290
  }
20227
20291
  _handleTransmuxComplete(transmuxResult) {
20228
20292
  var _id3$samples;
20229
- const id = 'main';
20293
+ const id = this.playlistType;
20230
20294
  const {
20231
20295
  hls
20232
20296
  } = this;
@@ -20431,31 +20495,38 @@ class StreamController extends BaseStreamController {
20431
20495
  audio.levelCodec = audioCodec;
20432
20496
  audio.id = 'main';
20433
20497
  this.log(`Init audio buffer, container:${audio.container}, codecs[selected/level/parsed]=[${audioCodec || ''}/${currentLevel.audioCodec || ''}/${audio.codec}]`);
20498
+ delete tracks.audiovideo;
20434
20499
  }
20435
20500
  if (video) {
20436
20501
  video.levelCodec = currentLevel.videoCodec;
20437
20502
  video.id = 'main';
20438
20503
  this.log(`Init video buffer, container:${video.container}, codecs[level/parsed]=[${currentLevel.videoCodec || ''}/${video.codec}]`);
20504
+ delete tracks.audiovideo;
20439
20505
  }
20440
20506
  if (audiovideo) {
20441
20507
  this.log(`Init audiovideo buffer, container:${audiovideo.container}, codecs[level/parsed]=[${currentLevel.codecs}/${audiovideo.codec}]`);
20508
+ delete tracks.video;
20509
+ delete tracks.audio;
20510
+ }
20511
+ const trackTypes = Object.keys(tracks);
20512
+ if (trackTypes.length) {
20513
+ this.hls.trigger(Events.BUFFER_CODECS, tracks);
20514
+ // loop through tracks that are going to be provided to bufferController
20515
+ trackTypes.forEach(trackName => {
20516
+ const track = tracks[trackName];
20517
+ const initSegment = track.initSegment;
20518
+ if (initSegment != null && initSegment.byteLength) {
20519
+ this.hls.trigger(Events.BUFFER_APPENDING, {
20520
+ type: trackName,
20521
+ data: initSegment,
20522
+ frag,
20523
+ part: null,
20524
+ chunkMeta,
20525
+ parent: frag.type
20526
+ });
20527
+ }
20528
+ });
20442
20529
  }
20443
- this.hls.trigger(Events.BUFFER_CODECS, tracks);
20444
- // loop through tracks that are going to be provided to bufferController
20445
- Object.keys(tracks).forEach(trackName => {
20446
- const track = tracks[trackName];
20447
- const initSegment = track.initSegment;
20448
- if (initSegment != null && initSegment.byteLength) {
20449
- this.hls.trigger(Events.BUFFER_APPENDING, {
20450
- type: trackName,
20451
- data: initSegment,
20452
- frag,
20453
- part: null,
20454
- chunkMeta,
20455
- parent: frag.type
20456
- });
20457
- }
20458
- });
20459
20530
  // trigger handler right now
20460
20531
  this.tickImmediate();
20461
20532
  }
@@ -20532,20 +20603,28 @@ class StreamController extends BaseStreamController {
20532
20603
  return -1;
20533
20604
  }
20534
20605
  get currentFrag() {
20535
- const media = this.media;
20536
- if (media) {
20537
- return this.fragPlaying || this.getAppendedFrag(media.currentTime);
20606
+ var _this$media2;
20607
+ if (this.fragPlaying) {
20608
+ return this.fragPlaying;
20609
+ }
20610
+ const currentTime = ((_this$media2 = this.media) == null ? void 0 : _this$media2.currentTime) || this.lastCurrentTime;
20611
+ if (isFiniteNumber(currentTime)) {
20612
+ return this.getAppendedFrag(currentTime);
20538
20613
  }
20539
20614
  return null;
20540
20615
  }
20541
20616
  get currentProgramDateTime() {
20542
- const media = this.media;
20543
- if (media) {
20544
- const currentTime = media.currentTime;
20545
- const frag = this.currentFrag;
20546
- if (frag && isFiniteNumber(currentTime) && isFiniteNumber(frag.programDateTime)) {
20547
- const epocMs = frag.programDateTime + (currentTime - frag.start) * 1000;
20548
- return new Date(epocMs);
20617
+ var _this$media3;
20618
+ const currentTime = ((_this$media3 = this.media) == null ? void 0 : _this$media3.currentTime) || this.lastCurrentTime;
20619
+ if (isFiniteNumber(currentTime)) {
20620
+ const details = this.getLevelDetails();
20621
+ const frag = this.currentFrag || (details ? findFragmentByPTS(null, details.fragments, currentTime) : null);
20622
+ if (frag) {
20623
+ const programDateTime = frag.programDateTime;
20624
+ if (programDateTime !== null) {
20625
+ const epocMs = programDateTime + (currentTime - frag.start) * 1000;
20626
+ return new Date(epocMs);
20627
+ }
20549
20628
  }
20550
20629
  }
20551
20630
  return null;
@@ -20578,7 +20657,7 @@ class Hls {
20578
20657
  * Get the video-dev/hls.js package version.
20579
20658
  */
20580
20659
  static get version() {
20581
- return "1.5.12-0.canary.10366";
20660
+ return "1.5.12-0.canary.10367";
20582
20661
  }
20583
20662
 
20584
20663
  /**
@@ -20709,7 +20788,7 @@ class Hls {
20709
20788
  if (AudioStreamControllerClass) {
20710
20789
  networkControllers.push(new AudioStreamControllerClass(this, fragmentTracker, keyLoader));
20711
20790
  }
20712
- // subtitleTrackController must be defined before subtitleStreamController because the order of event handling is important
20791
+ // Instantiate subtitleTrackController before SubtitleStreamController to receive level events first
20713
20792
  this.subtitleTrackController = this.createController(config.subtitleTrackController, networkControllers);
20714
20793
  const SubtitleStreamControllerClass = config.subtitleStreamController;
20715
20794
  if (SubtitleStreamControllerClass) {