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.
package/dist/hls.mjs CHANGED
@@ -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();
@@ -3937,10 +3937,9 @@ class PlaylistLoader {
3937
3937
  type
3938
3938
  } = context;
3939
3939
  const url = getResponseUrl(response, context);
3940
- const levelUrlId = 0;
3941
3940
  const levelId = isFiniteNumber(level) ? level : isFiniteNumber(id) ? id : 0;
3942
3941
  const levelType = mapContextToLevelType(context);
3943
- const levelDetails = M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, levelUrlId, this.variableList);
3942
+ const levelDetails = M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, 0, this.variableList);
3944
3943
 
3945
3944
  // We have done our first request (Manifest-type) and receive
3946
3945
  // not a master playlist but a chunk-list (track/level)
@@ -5830,8 +5829,7 @@ function computeReloadInterval(newDetails, distanceToLiveEdgeMs = Infinity) {
5830
5829
  }
5831
5830
  return Math.round(reloadInterval);
5832
5831
  }
5833
- function getFragmentWithSN(level, sn, fragCurrent) {
5834
- const details = level == null ? void 0 : level.details;
5832
+ function getFragmentWithSN(details, sn, fragCurrent) {
5835
5833
  if (!details) {
5836
5834
  return null;
5837
5835
  }
@@ -5848,12 +5846,11 @@ function getFragmentWithSN(level, sn, fragCurrent) {
5848
5846
  }
5849
5847
  return null;
5850
5848
  }
5851
- function getPartWith(level, sn, partIndex) {
5852
- var _level$details;
5853
- if (!(level != null && level.details)) {
5849
+ function getPartWith(details, sn, partIndex) {
5850
+ if (!details) {
5854
5851
  return null;
5855
5852
  }
5856
- return findPart((_level$details = level.details) == null ? void 0 : _level$details.partList, sn, partIndex);
5853
+ return findPart(details.partList, sn, partIndex);
5857
5854
  }
5858
5855
  function findPart(partList, sn, partIndex) {
5859
5856
  if (partList) {
@@ -5997,12 +5994,15 @@ function findFragmentByPDT(fragments, PDTValue, maxFragLookUpTolerance) {
5997
5994
  function findFragmentByPTS(fragPrevious, fragments, bufferEnd = 0, maxFragLookUpTolerance = 0, nextFragLookupTolerance = 0.005) {
5998
5995
  let fragNext = null;
5999
5996
  if (fragPrevious) {
6000
- fragNext = fragments[fragPrevious.sn - fragments[0].sn + 1] || null;
5997
+ fragNext = fragments[1 + fragPrevious.sn - fragments[0].sn] || null;
6001
5998
  // check for buffer-end rounding error
6002
5999
  const bufferEdgeError = fragPrevious.endDTS - bufferEnd;
6003
6000
  if (bufferEdgeError > 0 && bufferEdgeError < 0.0000015) {
6004
6001
  bufferEnd += 0.0000015;
6005
6002
  }
6003
+ if (fragNext && fragPrevious.level !== fragNext.level && fragNext.end <= fragPrevious.end) {
6004
+ fragNext = fragments[2 + fragPrevious.sn - fragments[0].sn] || null;
6005
+ }
6006
6006
  } else if (bufferEnd === 0 && fragments[0].start === 0) {
6007
6007
  fragNext = fragments[0];
6008
6008
  }
@@ -6093,6 +6093,24 @@ function findFragWithCC(fragments, cc) {
6093
6093
  }
6094
6094
  });
6095
6095
  }
6096
+ function findNearestWithCC(details, cc, fragment) {
6097
+ if (details) {
6098
+ if (details.startCC <= cc && details.endCC >= cc) {
6099
+ const start = fragment.start;
6100
+ const end = fragment.end;
6101
+ return BinarySearch.search(details.fragments, candidate => {
6102
+ if (candidate.cc < cc || candidate.end <= start) {
6103
+ return 1;
6104
+ } else if (candidate.cc > cc || candidate.start >= end) {
6105
+ return -1;
6106
+ } else {
6107
+ return 0;
6108
+ }
6109
+ });
6110
+ }
6111
+ }
6112
+ return null;
6113
+ }
6096
6114
 
6097
6115
  var NetworkErrorAction = {
6098
6116
  DoNothing: 0,
@@ -6154,7 +6172,7 @@ class ErrorController extends Logger {
6154
6172
  this.playlistError = 0;
6155
6173
  }
6156
6174
  onError(event, data) {
6157
- var _data$frag, _data$level;
6175
+ var _data$frag;
6158
6176
  if (data.fatal) {
6159
6177
  return;
6160
6178
  }
@@ -6170,10 +6188,7 @@ class ErrorController extends Logger {
6170
6188
  case ErrorDetails.FRAG_PARSING_ERROR:
6171
6189
  // ignore empty segment errors marked as gap
6172
6190
  if ((_data$frag = data.frag) != null && _data$frag.gap) {
6173
- data.errorAction = {
6174
- action: NetworkErrorAction.DoNothing,
6175
- flags: ErrorActionFlags.None
6176
- };
6191
+ data.errorAction = createDoNothingErrorAction();
6177
6192
  return;
6178
6193
  }
6179
6194
  // falls through
@@ -6240,7 +6255,11 @@ class ErrorController extends Logger {
6240
6255
  case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
6241
6256
  case ErrorDetails.REMUX_ALLOC_ERROR:
6242
6257
  case ErrorDetails.BUFFER_APPEND_ERROR:
6243
- data.errorAction = this.getLevelSwitchAction(data, (_data$level = data.level) != null ? _data$level : hls.loadLevel);
6258
+ // Buffer-controller can set errorAction when append errors can be ignored or resolved locally
6259
+ if (!data.errorAction) {
6260
+ var _data$level;
6261
+ data.errorAction = this.getLevelSwitchAction(data, (_data$level = data.level) != null ? _data$level : hls.loadLevel);
6262
+ }
6244
6263
  return;
6245
6264
  case ErrorDetails.INTERNAL_EXCEPTION:
6246
6265
  case ErrorDetails.BUFFER_APPENDING_ERROR:
@@ -6249,10 +6268,7 @@ class ErrorController extends Logger {
6249
6268
  case ErrorDetails.BUFFER_STALLED_ERROR:
6250
6269
  case ErrorDetails.BUFFER_SEEK_OVER_HOLE:
6251
6270
  case ErrorDetails.BUFFER_NUDGE_ON_STALL:
6252
- data.errorAction = {
6253
- action: NetworkErrorAction.DoNothing,
6254
- flags: ErrorActionFlags.None
6255
- };
6271
+ data.errorAction = createDoNothingErrorAction();
6256
6272
  return;
6257
6273
  }
6258
6274
  if (data.type === ErrorTypes.KEY_SYSTEM_ERROR) {
@@ -6465,6 +6481,13 @@ class ErrorController extends Logger {
6465
6481
  }
6466
6482
  }
6467
6483
  }
6484
+ function createDoNothingErrorAction(resolved) {
6485
+ const errorAction = {
6486
+ action: NetworkErrorAction.DoNothing,
6487
+ flags: ErrorActionFlags.None
6488
+ };
6489
+ return errorAction;
6490
+ }
6468
6491
 
6469
6492
  class BasePlaylistController extends Logger {
6470
6493
  constructor(hls, logPrefix) {
@@ -7042,6 +7065,7 @@ function getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPrefer
7042
7065
  if (!hasCurrentVideoRange) {
7043
7066
  currentVideoRange = undefined;
7044
7067
  }
7068
+ const hasMultipleSets = codecSets.length > 1;
7045
7069
  const codecSet = codecSets.reduce((selected, candidate) => {
7046
7070
  // 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
7047
7071
  const candidateTier = codecTiers[candidate];
@@ -7049,46 +7073,48 @@ function getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPrefer
7049
7073
  return selected;
7050
7074
  }
7051
7075
  videoRanges = hasCurrentVideoRange ? allowedVideoRanges.filter(range => candidateTier.videoRanges[range] > 0) : [];
7052
- if (candidateTier.minBitrate > currentBw) {
7053
- logStartCodecCandidateIgnored(candidate, `min bitrate of ${candidateTier.minBitrate} > current estimate of ${currentBw}`);
7054
- return selected;
7055
- }
7056
- if (!candidateTier.hasDefaultAudio) {
7057
- logStartCodecCandidateIgnored(candidate, `no renditions with default or auto-select sound found`);
7058
- return selected;
7059
- }
7060
- if (audioCodecPreference && candidate.indexOf(audioCodecPreference.substring(0, 4)) % 5 !== 0) {
7061
- logStartCodecCandidateIgnored(candidate, `audio codec preference "${audioCodecPreference}" not found`);
7062
- return selected;
7063
- }
7064
- if (channelsPreference && !preferStereo) {
7065
- if (!candidateTier.channels[channelsPreference]) {
7066
- logStartCodecCandidateIgnored(candidate, `no renditions with ${channelsPreference} channel sound found (channels options: ${Object.keys(candidateTier.channels)})`);
7076
+ if (hasMultipleSets) {
7077
+ if (candidateTier.minBitrate > currentBw) {
7078
+ logStartCodecCandidateIgnored(candidate, `min bitrate of ${candidateTier.minBitrate} > current estimate of ${currentBw}`);
7079
+ return selected;
7080
+ }
7081
+ if (!candidateTier.hasDefaultAudio) {
7082
+ logStartCodecCandidateIgnored(candidate, `no renditions with default or auto-select sound found`);
7083
+ return selected;
7084
+ }
7085
+ if (audioCodecPreference && candidate.indexOf(audioCodecPreference.substring(0, 4)) % 5 !== 0) {
7086
+ logStartCodecCandidateIgnored(candidate, `audio codec preference "${audioCodecPreference}" not found`);
7087
+ return selected;
7088
+ }
7089
+ if (channelsPreference && !preferStereo) {
7090
+ if (!candidateTier.channels[channelsPreference]) {
7091
+ logStartCodecCandidateIgnored(candidate, `no renditions with ${channelsPreference} channel sound found (channels options: ${Object.keys(candidateTier.channels)})`);
7092
+ return selected;
7093
+ }
7094
+ } else if ((!audioCodecPreference || preferStereo) && hasStereo && candidateTier.channels['2'] === 0) {
7095
+ logStartCodecCandidateIgnored(candidate, `no renditions with stereo sound found`);
7096
+ return selected;
7097
+ }
7098
+ if (candidateTier.minHeight > maxHeight) {
7099
+ logStartCodecCandidateIgnored(candidate, `min resolution of ${candidateTier.minHeight} > maximum of ${maxHeight}`);
7100
+ return selected;
7101
+ }
7102
+ if (candidateTier.minFramerate > maxFramerate) {
7103
+ logStartCodecCandidateIgnored(candidate, `min framerate of ${candidateTier.minFramerate} > maximum of ${maxFramerate}`);
7104
+ return selected;
7105
+ }
7106
+ if (!videoRanges.some(range => candidateTier.videoRanges[range] > 0)) {
7107
+ logStartCodecCandidateIgnored(candidate, `no variants with VIDEO-RANGE of ${JSON.stringify(videoRanges)} found`);
7108
+ return selected;
7109
+ }
7110
+ if (videoCodecPreference && candidate.indexOf(videoCodecPreference.substring(0, 4)) % 5 !== 0) {
7111
+ logStartCodecCandidateIgnored(candidate, `video codec preference "${videoCodecPreference}" not found`);
7112
+ return selected;
7113
+ }
7114
+ if (candidateTier.maxScore < selectedScore) {
7115
+ logStartCodecCandidateIgnored(candidate, `max score of ${candidateTier.maxScore} < selected max of ${selectedScore}`);
7067
7116
  return selected;
7068
7117
  }
7069
- } else if ((!audioCodecPreference || preferStereo) && hasStereo && candidateTier.channels['2'] === 0) {
7070
- logStartCodecCandidateIgnored(candidate, `no renditions with stereo sound found`);
7071
- return selected;
7072
- }
7073
- if (candidateTier.minHeight > maxHeight) {
7074
- logStartCodecCandidateIgnored(candidate, `min resolution of ${candidateTier.minHeight} > maximum of ${maxHeight}`);
7075
- return selected;
7076
- }
7077
- if (candidateTier.minFramerate > maxFramerate) {
7078
- logStartCodecCandidateIgnored(candidate, `min framerate of ${candidateTier.minFramerate} > maximum of ${maxFramerate}`);
7079
- return selected;
7080
- }
7081
- if (!videoRanges.some(range => candidateTier.videoRanges[range] > 0)) {
7082
- logStartCodecCandidateIgnored(candidate, `no variants with VIDEO-RANGE of ${JSON.stringify(videoRanges)} found`);
7083
- return selected;
7084
- }
7085
- if (videoCodecPreference && candidate.indexOf(videoCodecPreference.substring(0, 4)) % 5 !== 0) {
7086
- logStartCodecCandidateIgnored(candidate, `video codec preference "${videoCodecPreference}" not found`);
7087
- return selected;
7088
- }
7089
- if (candidateTier.maxScore < selectedScore) {
7090
- logStartCodecCandidateIgnored(candidate, `max score of ${candidateTier.maxScore} < selected max of ${selectedScore}`);
7091
- return selected;
7092
7118
  }
7093
7119
  // Remove candiates with less preferred codecs or more errors
7094
7120
  if (selected && (codecsSetSelectionPreferenceValue(candidate) >= codecsSetSelectionPreferenceValue(selected) || candidateTier.fragmentError > codecTiers[selected].fragmentError)) {
@@ -7895,7 +7921,7 @@ class AbrController extends Logger {
7895
7921
  if (levelsSkipped.length) {
7896
7922
  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}`);
7897
7923
  }
7898
- 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}`);
7924
+ 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}`);
7899
7925
  }
7900
7926
  if (firstSelection) {
7901
7927
  this.firstSelection = i;
@@ -8064,6 +8090,7 @@ class FragmentTracker {
8064
8090
  const {
8065
8091
  hls
8066
8092
  } = this;
8093
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
8067
8094
  hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
8068
8095
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
8069
8096
  hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
@@ -8072,6 +8099,7 @@ class FragmentTracker {
8072
8099
  const {
8073
8100
  hls
8074
8101
  } = this;
8102
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
8075
8103
  hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
8076
8104
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
8077
8105
  hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
@@ -8137,7 +8165,7 @@ class FragmentTracker {
8137
8165
  * The browser will unload parts of the buffer to free up memory for new buffer data
8138
8166
  * 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)
8139
8167
  */
8140
- detectEvictedFragments(elementaryStream, timeRange, playlistType, appendedPart) {
8168
+ detectEvictedFragments(elementaryStream, timeRange, playlistType, appendedPart, removeAppending) {
8141
8169
  if (this.timeRanges) {
8142
8170
  this.timeRanges[elementaryStream] = timeRange;
8143
8171
  }
@@ -8152,7 +8180,7 @@ class FragmentTracker {
8152
8180
  if (appendedPartSn >= fragmentEntity.body.sn) {
8153
8181
  return;
8154
8182
  }
8155
- if (!fragmentEntity.buffered && !fragmentEntity.loaded) {
8183
+ if (!fragmentEntity.buffered && (!fragmentEntity.loaded || removeAppending)) {
8156
8184
  if (fragmentEntity.body.type === playlistType) {
8157
8185
  this.removeFragment(fragmentEntity.body);
8158
8186
  }
@@ -8162,6 +8190,10 @@ class FragmentTracker {
8162
8190
  if (!esData) {
8163
8191
  return;
8164
8192
  }
8193
+ if (esData.time.length === 0) {
8194
+ this.removeFragment(fragmentEntity.body);
8195
+ return;
8196
+ }
8165
8197
  esData.time.some(time => {
8166
8198
  const isNotBuffered = !this.isTimeBuffered(time.startPTS, time.endPTS, timeRange);
8167
8199
  if (isNotBuffered) {
@@ -8348,6 +8380,9 @@ class FragmentTracker {
8348
8380
  }
8349
8381
  return false;
8350
8382
  }
8383
+ onManifestLoading() {
8384
+ this.removeAllFragments();
8385
+ }
8351
8386
  onFragLoaded(event, data) {
8352
8387
  // don't track initsegment (for which sn is not a number)
8353
8388
  // don't track frags used for bitrateTest, they're irrelevant.
@@ -8396,6 +8431,22 @@ class FragmentTracker {
8396
8431
  const fragKey = getFragmentKey(fragment);
8397
8432
  return !!this.fragments[fragKey];
8398
8433
  }
8434
+ hasFragments(type) {
8435
+ const {
8436
+ fragments
8437
+ } = this;
8438
+ const keys = Object.keys(fragments);
8439
+ if (!type) {
8440
+ return keys.length > 0;
8441
+ }
8442
+ for (let i = keys.length; i--;) {
8443
+ const fragmentEntity = fragments[keys[i]];
8444
+ if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === type) {
8445
+ return true;
8446
+ }
8447
+ }
8448
+ return false;
8449
+ }
8399
8450
  hasParts(type) {
8400
8451
  var _this$activePartLists;
8401
8452
  return !!((_this$activePartLists = this.activePartLists[type]) != null && _this$activePartLists.length);
@@ -8800,8 +8851,9 @@ class FragmentLoader {
8800
8851
  frag.gap = false;
8801
8852
  }
8802
8853
  }
8803
- const loader = this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
8854
+ const loader = this.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
8804
8855
  const loaderContext = createLoaderContext(frag);
8856
+ frag.loader = loader;
8805
8857
  const loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default);
8806
8858
  const loaderConfig = {
8807
8859
  loadPolicy,
@@ -8894,8 +8946,9 @@ class FragmentLoader {
8894
8946
  reject(createGapLoadError(frag, part));
8895
8947
  return;
8896
8948
  }
8897
- const loader = this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
8949
+ const loader = this.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
8898
8950
  const loaderContext = createLoaderContext(frag, part);
8951
+ frag.loader = loader;
8899
8952
  // Should we define another load policy for parts?
8900
8953
  const loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default);
8901
8954
  const loaderConfig = {
@@ -9590,6 +9643,7 @@ class BaseStreamController extends TaskLoop {
9590
9643
  this.initPTS = [];
9591
9644
  this.buffering = true;
9592
9645
  this.loadingParts = false;
9646
+ this.loopSn = void 0;
9593
9647
  this.onMediaSeeking = () => {
9594
9648
  const {
9595
9649
  config,
@@ -9689,6 +9743,9 @@ class BaseStreamController extends TaskLoop {
9689
9743
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
9690
9744
  startLoad(startPosition) {}
9691
9745
  stopLoad() {
9746
+ if (this.state === State.STOPPED) {
9747
+ return;
9748
+ }
9692
9749
  this.fragmentLoader.abort();
9693
9750
  this.keyLoader.abort(this.playlistType);
9694
9751
  const frag = this.fragCurrent;
@@ -9762,15 +9819,20 @@ class BaseStreamController extends TaskLoop {
9762
9819
  this.keyLoader.detach();
9763
9820
  }
9764
9821
  this.media = this.mediaBuffer = null;
9765
- this.loadedmetadata = false;
9822
+ this.loopSn = undefined;
9823
+ this.startFragRequested = this.loadedmetadata = this.loadingParts = false;
9766
9824
  this.fragmentTracker.removeAllFragments();
9767
9825
  this.stopLoad();
9768
9826
  }
9769
- onManifestLoading() {}
9827
+ onManifestLoading() {
9828
+ this.initPTS = [];
9829
+ this.levels = this.levelLastLoaded = this.fragCurrent = null;
9830
+ this.lastCurrentTime = this.startPosition = 0;
9831
+ this.startFragRequested = false;
9832
+ }
9770
9833
  onError(event, data) {}
9771
9834
  onManifestLoaded(event, data) {
9772
9835
  this.startTimeOffset = data.startTimeOffset;
9773
- this.initPTS = [];
9774
9836
  }
9775
9837
  onHandlerDestroying() {
9776
9838
  this.stopLoad();
@@ -9793,6 +9855,7 @@ class BaseStreamController extends TaskLoop {
9793
9855
  super.onHandlerDestroyed();
9794
9856
  }
9795
9857
  loadFragment(frag, level, targetBufferTime) {
9858
+ this.startFragRequested = true;
9796
9859
  this._loadFragForPlayback(frag, level, targetBufferTime);
9797
9860
  }
9798
9861
  _loadFragForPlayback(frag, level, targetBufferTime) {
@@ -10021,6 +10084,7 @@ class BaseStreamController extends TaskLoop {
10021
10084
  _handleFragmentLoadProgress(frag) {}
10022
10085
  _doFragLoad(frag, level, targetBufferTime = null, progressCallback) {
10023
10086
  var _frag$decryptdata;
10087
+ this.fragCurrent = frag;
10024
10088
  const details = level == null ? void 0 : level.details;
10025
10089
  if (!this.levels || !details) {
10026
10090
  throw new Error(`frag load aborted, missing level${details ? '' : ' detail'}s`);
@@ -10148,7 +10212,7 @@ class BaseStreamController extends TaskLoop {
10148
10212
  partsLoaded[part.index] = partLoadedData;
10149
10213
  const loadedPart = partLoadedData.part;
10150
10214
  this.hls.trigger(Events.FRAG_LOADED, partLoadedData);
10151
- const nextPart = getPartWith(level, frag.sn, part.index + 1) || findPart(initialPartList, frag.sn, part.index + 1);
10215
+ const nextPart = getPartWith(level.details, frag.sn, part.index + 1) || findPart(initialPartList, frag.sn, part.index + 1);
10152
10216
  if (nextPart) {
10153
10217
  loadPart(nextPart);
10154
10218
  } else {
@@ -10243,8 +10307,9 @@ class BaseStreamController extends TaskLoop {
10243
10307
  return null;
10244
10308
  }
10245
10309
  const level = levels[levelIndex];
10246
- const part = partIndex > -1 ? getPartWith(level, sn, partIndex) : null;
10247
- const frag = part ? part.fragment : getFragmentWithSN(level, sn, fragCurrent);
10310
+ const levelDetails = level.details;
10311
+ const part = partIndex > -1 ? getPartWith(levelDetails, sn, partIndex) : null;
10312
+ const frag = part ? part.fragment : getFragmentWithSN(levelDetails, sn, fragCurrent);
10248
10313
  if (!frag) {
10249
10314
  return null;
10250
10315
  }
@@ -10359,7 +10424,8 @@ class BaseStreamController extends TaskLoop {
10359
10424
  return false;
10360
10425
  }
10361
10426
  getAppendedFrag(position, playlistType = PlaylistLevelType.MAIN) {
10362
- const fragOrPart = this.fragmentTracker.getAppendedFrag(position, PlaylistLevelType.MAIN);
10427
+ var _this$fragmentTracker;
10428
+ const fragOrPart = (_this$fragmentTracker = this.fragmentTracker) == null ? void 0 : _this$fragmentTracker.getAppendedFrag(position, playlistType);
10363
10429
  if (fragOrPart && 'fragment' in fragOrPart) {
10364
10430
  return fragOrPart.fragment;
10365
10431
  }
@@ -10414,22 +10480,25 @@ class BaseStreamController extends TaskLoop {
10414
10480
  return (trackerState === FragmentState.OK || trackerState === FragmentState.PARTIAL && !!frag.gap) && this.nextLoadPosition > targetBufferTime;
10415
10481
  }
10416
10482
  getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, playlistType, maxBufLen) {
10417
- const gapStart = frag.gap;
10418
- const nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails);
10419
- if (nextFragment === null) {
10420
- return nextFragment;
10421
- }
10422
- frag = nextFragment;
10423
- if (gapStart && frag && !frag.gap && bufferInfo.nextStart) {
10424
- // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length
10425
- const nextbufferInfo = this.getFwdBufferInfoAtPos(this.mediaBuffer ? this.mediaBuffer : this.media, bufferInfo.nextStart, playlistType);
10426
- if (nextbufferInfo !== null && bufferInfo.len + nextbufferInfo.len >= maxBufLen) {
10427
- // Returning here might result in not finding an audio and video candiate to skip to
10428
- this.log(`buffer full after gaps in "${playlistType}" playlist starting at sn: ${frag.sn}`);
10429
- return null;
10483
+ let nextFragment = null;
10484
+ if (frag.gap) {
10485
+ nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails);
10486
+ if (nextFragment && !nextFragment.gap && bufferInfo.nextStart) {
10487
+ // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length
10488
+ const nextbufferInfo = this.getFwdBufferInfoAtPos(this.mediaBuffer ? this.mediaBuffer : this.media, bufferInfo.nextStart, playlistType);
10489
+ if (nextbufferInfo !== null && bufferInfo.len + nextbufferInfo.len >= maxBufLen) {
10490
+ // Returning here might result in not finding an audio and video candiate to skip to
10491
+ const sn = nextFragment.sn;
10492
+ if (this.loopSn !== sn) {
10493
+ this.log(`buffer full after gaps in "${playlistType}" playlist starting at sn: ${sn}`);
10494
+ this.loopSn = sn;
10495
+ }
10496
+ return null;
10497
+ }
10430
10498
  }
10431
10499
  }
10432
- return frag;
10500
+ this.loopSn = undefined;
10501
+ return nextFragment;
10433
10502
  }
10434
10503
  mapToInitFragWhenRequired(frag) {
10435
10504
  // If an initSegment is present, it must be buffered first
@@ -10638,7 +10707,7 @@ class BaseStreamController extends TaskLoop {
10638
10707
  if (startTimeOffset !== null && isFiniteNumber(startTimeOffset)) {
10639
10708
  startPosition = sliding + startTimeOffset;
10640
10709
  if (startTimeOffset < 0) {
10641
- startPosition += details.totalduration;
10710
+ startPosition += details.edge;
10642
10711
  }
10643
10712
  startPosition = Math.min(Math.max(sliding, startPosition), sliding + details.totalduration);
10644
10713
  this.log(`Start time offset ${startTimeOffset} found in ${offsetInMultivariantPlaylist ? 'multivariant' : 'media'} playlist, adjust startPosition to ${startPosition}`);
@@ -14980,10 +15049,11 @@ class MP4Remuxer {
14980
15049
  if (this.ISGenerated) {
14981
15050
  var _videoTrack$pixelRati, _config$pixelRatio, _videoTrack$pixelRati2, _config$pixelRatio2;
14982
15051
  const config = this.videoTrackConfig;
14983
- 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]))) {
15052
+ 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) {
14984
15053
  this.resetInitSegment();
14985
15054
  }
14986
- } else {
15055
+ }
15056
+ if (!this.ISGenerated) {
14987
15057
  initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);
14988
15058
  }
14989
15059
  const isVideoContiguous = this.isVideoContiguous;
@@ -15901,7 +15971,7 @@ class PassThroughRemuxer {
15901
15971
  if (isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) || initSegment.timescale !== initPTS.timescale && accurateTimeOffset) {
15902
15972
  initSegment.initPTS = decodeTime - timeOffset;
15903
15973
  if (initPTS && initPTS.timescale === 1) {
15904
- logger.warn(`Adjusting initPTS by ${initSegment.initPTS - initPTS.baseTime}`);
15974
+ logger.warn(`Adjusting initPTS @${timeOffset} from ${initPTS.baseTime / initPTS.timescale} to ${initSegment.initPTS}`);
15905
15975
  }
15906
15976
  this.initPTS = initPTS = {
15907
15977
  baseTime: initSegment.initPTS,
@@ -17035,9 +17105,8 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
17035
17105
  class AudioStreamController extends BaseStreamController {
17036
17106
  constructor(hls, fragmentTracker, keyLoader) {
17037
17107
  super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
17038
- this.videoBuffer = null;
17039
- this.videoTrackCC = -1;
17040
- this.waitingVideoCC = -1;
17108
+ this.videoAnchor = null;
17109
+ this.mainFragLoading = null;
17041
17110
  this.bufferedTrack = null;
17042
17111
  this.switchingTrack = null;
17043
17112
  this.trackId = -1;
@@ -17069,6 +17138,7 @@ class AudioStreamController extends BaseStreamController {
17069
17138
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
17070
17139
  hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
17071
17140
  hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
17141
+ hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
17072
17142
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
17073
17143
  }
17074
17144
  unregisterListeners() {
@@ -17088,6 +17158,7 @@ class AudioStreamController extends BaseStreamController {
17088
17158
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
17089
17159
  hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
17090
17160
  hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
17161
+ hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
17091
17162
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
17092
17163
  }
17093
17164
 
@@ -17100,20 +17171,35 @@ class AudioStreamController extends BaseStreamController {
17100
17171
  }) {
17101
17172
  // Always update the new INIT PTS
17102
17173
  // Can change due level switch
17103
- if (id === 'main') {
17174
+ if (id === PlaylistLevelType.MAIN) {
17104
17175
  const cc = frag.cc;
17105
- this.initPTS[frag.cc] = {
17176
+ const inFlightFrag = this.fragCurrent;
17177
+ this.initPTS[cc] = {
17106
17178
  baseTime: initPTS,
17107
17179
  timescale
17108
17180
  };
17109
17181
  this.log(`InitPTS for cc: ${cc} found from main: ${initPTS}/${timescale}`);
17110
- this.videoTrackCC = cc;
17182
+ this.videoAnchor = frag;
17111
17183
  // If we are waiting, tick immediately to unblock audio fragment transmuxing
17112
17184
  if (this.state === State.WAITING_INIT_PTS) {
17185
+ const waitingData = this.waitingData;
17186
+ if (!waitingData || waitingData.frag.cc !== cc) {
17187
+ this.nextLoadPosition = this.findSyncFrag(frag).start;
17188
+ }
17113
17189
  this.tick();
17190
+ } else if (!this.loadedmetadata && inFlightFrag && inFlightFrag.cc !== cc) {
17191
+ this.startFragRequested = false;
17192
+ this.nextLoadPosition = this.findSyncFrag(frag).start;
17193
+ inFlightFrag.abortRequests();
17194
+ this.resetLoadingState();
17114
17195
  }
17115
17196
  }
17116
17197
  }
17198
+ findSyncFrag(mainFrag) {
17199
+ const trackDetails = this.getLevelDetails();
17200
+ const cc = mainFrag.cc;
17201
+ return findNearestWithCC(trackDetails, cc, mainFrag) || trackDetails && findFragWithCC(trackDetails.fragments, cc) || mainFrag;
17202
+ }
17117
17203
  startLoad(startPosition) {
17118
17204
  if (!this.levels) {
17119
17205
  this.startPosition = startPosition;
@@ -17183,9 +17269,9 @@ class AudioStreamController extends BaseStreamController {
17183
17269
  cache,
17184
17270
  complete
17185
17271
  } = waitingData;
17272
+ const videoAnchor = this.videoAnchor;
17186
17273
  if (this.initPTS[frag.cc] !== undefined) {
17187
17274
  this.waitingData = null;
17188
- this.waitingVideoCC = -1;
17189
17275
  this.state = State.FRAG_LOADING;
17190
17276
  const payload = cache.flush();
17191
17277
  const data = {
@@ -17198,21 +17284,13 @@ class AudioStreamController extends BaseStreamController {
17198
17284
  if (complete) {
17199
17285
  super._handleFragmentLoadComplete(data);
17200
17286
  }
17201
- } else if (this.videoTrackCC !== this.waitingVideoCC) {
17287
+ } else if (videoAnchor && videoAnchor.cc !== waitingData.frag.cc) {
17202
17288
  // Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found
17203
- this.log(`Waiting fragment cc (${frag.cc}) cancelled because video is at cc ${this.videoTrackCC}`);
17289
+ this.log(`Waiting fragment cc (${frag.cc}) cancelled because video is at cc ${videoAnchor.cc}`);
17290
+ this.nextLoadPosition = this.findSyncFrag(videoAnchor).start;
17204
17291
  this.clearWaitingFragment();
17205
- } else {
17206
- // Drop waiting fragment if an earlier fragment is needed
17207
- const pos = this.getLoadPosition();
17208
- const bufferInfo = BufferHelper.bufferInfo(this.mediaBuffer, pos, this.config.maxBufferHole);
17209
- const waitingFragmentAtPosition = fragmentWithinToleranceTest(bufferInfo.end, this.config.maxFragLookUpTolerance, frag);
17210
- if (waitingFragmentAtPosition < 0) {
17211
- this.log(`Waiting fragment cc (${frag.cc}) @ ${frag.start} cancelled because another fragment at ${bufferInfo.end} is needed`);
17212
- this.clearWaitingFragment();
17213
- }
17214
17292
  }
17215
- } else {
17293
+ } else if (this.state !== State.STOPPED) {
17216
17294
  this.state = State.IDLE;
17217
17295
  }
17218
17296
  }
@@ -17222,9 +17300,12 @@ class AudioStreamController extends BaseStreamController {
17222
17300
  clearWaitingFragment() {
17223
17301
  const waitingData = this.waitingData;
17224
17302
  if (waitingData) {
17303
+ if (!this.loadedmetadata) {
17304
+ // Load overlapping fragment on start when discontinuity start times are not aligned
17305
+ this.startFragRequested = false;
17306
+ }
17225
17307
  this.fragmentTracker.removeFragment(waitingData.frag);
17226
17308
  this.waitingData = null;
17227
- this.waitingVideoCC = -1;
17228
17309
  if (this.state !== State.STOPPED) {
17229
17310
  this.state = State.IDLE;
17230
17311
  }
@@ -17292,9 +17373,10 @@ class AudioStreamController extends BaseStreamController {
17292
17373
  const maxBufLen = hls.maxBufferLength;
17293
17374
  const fragments = trackDetails.fragments;
17294
17375
  const start = fragments[0].start;
17295
- let targetBufferTime = this.flushing ? this.getLoadPosition() : bufferInfo.end;
17376
+ const loadPosition = this.getLoadPosition();
17377
+ let targetBufferTime = this.flushing ? loadPosition : bufferInfo.end;
17296
17378
  if (switchingTrack && media) {
17297
- const pos = this.getLoadPosition();
17379
+ const pos = loadPosition;
17298
17380
  // STABLE
17299
17381
  if (bufferedTrack && !mediaAttributesIdentical(switchingTrack.attrs, bufferedTrack.attrs)) {
17300
17382
  targetBufferTime = pos;
@@ -17314,37 +17396,26 @@ class AudioStreamController extends BaseStreamController {
17314
17396
  return;
17315
17397
  }
17316
17398
  let frag = this.getNextFragment(targetBufferTime, trackDetails);
17317
- let atGap = false;
17318
17399
  // Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags
17319
17400
  if (frag && this.isLoopLoading(frag, targetBufferTime)) {
17320
- atGap = !!frag.gap;
17321
17401
  frag = this.getNextFragmentLoopLoading(frag, trackDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen);
17322
17402
  }
17323
17403
  if (!frag) {
17324
17404
  this.bufferFlushed = true;
17325
17405
  return;
17326
17406
  }
17327
- if (!trackDetails.live || targetBufferTime < this.hls.liveSyncPosition) {
17407
+ if (this.startFragRequested && (!trackDetails.live || targetBufferTime < this.hls.liveSyncPosition)) {
17328
17408
  // Request audio segments up to one fragment ahead of main buffer
17329
- const mainBufferInfo = this.getFwdBufferInfo(this.videoBuffer ? this.videoBuffer : this.media, PlaylistLevelType.MAIN);
17330
- const atBufferSyncLimit = !!mainBufferInfo && frag.start > mainBufferInfo.end + frag.duration;
17331
- if (atBufferSyncLimit) {
17332
- // Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo
17333
- const mainFrag = this.fragmentTracker.getFragAtPos(frag.start, PlaylistLevelType.MAIN);
17334
- if (mainFrag === null) {
17335
- return;
17336
- }
17337
- // Bridge gaps in main buffer (also prevents loop loading at gaps)
17338
- atGap || (atGap = !!mainFrag.gap || mainBufferInfo.len === 0);
17339
- if (!atGap || bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end) {
17340
- return;
17341
- }
17409
+ const mainFragLoading = this.mainFragLoading;
17410
+ const mainTargetBufferEnd = mainFragLoading ? (mainFragLoading.part || mainFragLoading.frag).end : null;
17411
+ const atBufferSyncLimit = mainTargetBufferEnd !== null && frag.start > mainTargetBufferEnd;
17412
+ if (atBufferSyncLimit && !frag.endList) {
17413
+ return;
17342
17414
  }
17343
17415
  }
17344
17416
  this.loadFragment(frag, levelInfo, targetBufferTime);
17345
17417
  }
17346
17418
  onMediaDetaching() {
17347
- this.videoBuffer = null;
17348
17419
  this.bufferFlushed = this.flushing = false;
17349
17420
  super.onMediaDetaching();
17350
17421
  }
@@ -17388,12 +17459,10 @@ class AudioStreamController extends BaseStreamController {
17388
17459
  }
17389
17460
  }
17390
17461
  onManifestLoading() {
17391
- this.fragmentTracker.removeAllFragments();
17392
- this.startPosition = this.lastCurrentTime = 0;
17462
+ super.onManifestLoading();
17393
17463
  this.bufferFlushed = this.flushing = false;
17394
- this.levels = this.mainDetails = this.waitingData = this.bufferedTrack = this.cachedTrackLoadedData = this.switchingTrack = null;
17395
- this.startFragRequested = false;
17396
- this.trackId = this.videoTrackCC = this.waitingVideoCC = -1;
17464
+ this.mainDetails = this.waitingData = this.videoAnchor = this.bufferedTrack = this.cachedTrackLoadedData = this.switchingTrack = null;
17465
+ this.trackId = -1;
17397
17466
  }
17398
17467
  onLevelLoaded(event, data) {
17399
17468
  this.mainDetails = data.details;
@@ -17509,7 +17578,6 @@ class AudioStreamController extends BaseStreamController {
17509
17578
  complete: false
17510
17579
  };
17511
17580
  cache.push(new Uint8Array(payload));
17512
- this.waitingVideoCC = this.videoTrackCC;
17513
17581
  this.state = State.WAITING_INIT_PTS;
17514
17582
  }
17515
17583
  }
@@ -17523,7 +17591,7 @@ class AudioStreamController extends BaseStreamController {
17523
17591
  onBufferReset( /* event: Events.BUFFER_RESET */
17524
17592
  ) {
17525
17593
  // reset reference to sourcebuffers
17526
- this.mediaBuffer = this.videoBuffer = null;
17594
+ this.mediaBuffer = null;
17527
17595
  this.loadedmetadata = false;
17528
17596
  }
17529
17597
  onBufferCreated(event, data) {
@@ -17531,8 +17599,13 @@ class AudioStreamController extends BaseStreamController {
17531
17599
  if (audioTrack) {
17532
17600
  this.mediaBuffer = audioTrack.buffer || null;
17533
17601
  }
17534
- if (data.tracks.video) {
17535
- this.videoBuffer = data.tracks.video.buffer || null;
17602
+ }
17603
+ onFragLoading(event, data) {
17604
+ if (data.frag.type === PlaylistLevelType.MAIN && data.frag.sn !== 'initSegment') {
17605
+ this.mainFragLoading = data;
17606
+ if (this.state === State.IDLE) {
17607
+ this.tick();
17608
+ }
17536
17609
  }
17537
17610
  }
17538
17611
  onFragBuffered(event, data) {
@@ -17542,12 +17615,9 @@ class AudioStreamController extends BaseStreamController {
17542
17615
  } = data;
17543
17616
  if (frag.type !== PlaylistLevelType.AUDIO) {
17544
17617
  if (!this.loadedmetadata && frag.type === PlaylistLevelType.MAIN) {
17545
- const bufferable = this.videoBuffer || this.media;
17546
- if (bufferable) {
17547
- const bufferedTimeRanges = BufferHelper.getBuffered(bufferable);
17548
- if (bufferedTimeRanges.length) {
17549
- this.loadedmetadata = true;
17550
- }
17618
+ const bufferedState = this.fragmentTracker.getState(frag);
17619
+ if (bufferedState === FragmentState.OK || bufferedState === FragmentState.PARTIAL) {
17620
+ this.loadedmetadata = true;
17551
17621
  }
17552
17622
  }
17553
17623
  return;
@@ -17728,12 +17798,15 @@ class AudioStreamController extends BaseStreamController {
17728
17798
  if (tracks.video) {
17729
17799
  delete tracks.video;
17730
17800
  }
17801
+ if (tracks.audiovideo) {
17802
+ delete tracks.audiovideo;
17803
+ }
17731
17804
 
17732
17805
  // include levelCodec in audio and video tracks
17733
- const track = tracks.audio;
17734
- if (!track) {
17806
+ if (!tracks.audio) {
17735
17807
  return;
17736
17808
  }
17809
+ const track = tracks.audio;
17737
17810
  track.id = 'audio';
17738
17811
  const variantAudioCodecs = currentLevel.audioCodec;
17739
17812
  this.log(`Init audio buffer, container:${track.container}, codecs[level/parsed]=[${variantAudioCodecs}/${track.codec}]`);
@@ -17760,7 +17833,6 @@ class AudioStreamController extends BaseStreamController {
17760
17833
  loadFragment(frag, track, targetBufferTime) {
17761
17834
  // only load if fragment is not loaded or if in audio switch
17762
17835
  const fragState = this.fragmentTracker.getState(frag);
17763
- this.fragCurrent = frag;
17764
17836
 
17765
17837
  // we force a frag loading in audio switch as fragment tracker might not have evicted previous frags in case of quick audio switch
17766
17838
  if (this.switchingTrack || fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) {
@@ -17775,7 +17847,6 @@ class AudioStreamController extends BaseStreamController {
17775
17847
  alignMediaPlaylistByPDT(track.details, mainDetails);
17776
17848
  }
17777
17849
  } else {
17778
- this.startFragRequested = true;
17779
17850
  super.loadFragment(frag, track, targetBufferTime);
17780
17851
  }
17781
17852
  } else {
@@ -18172,8 +18243,8 @@ class SubtitleStreamController extends BaseStreamController {
18172
18243
  this.tick();
18173
18244
  }
18174
18245
  onManifestLoading() {
18246
+ super.onManifestLoading();
18175
18247
  this.mainDetails = null;
18176
- this.fragmentTracker.removeAllFragments();
18177
18248
  }
18178
18249
  onMediaDetaching() {
18179
18250
  this.tracksBuffered = [];
@@ -18187,7 +18258,9 @@ class SubtitleStreamController extends BaseStreamController {
18187
18258
  frag,
18188
18259
  success
18189
18260
  } = data;
18190
- this.fragPrevious = frag;
18261
+ if (frag.sn !== 'initSegment') {
18262
+ this.fragPrevious = frag;
18263
+ }
18191
18264
  this.state = State.IDLE;
18192
18265
  if (!success) {
18193
18266
  return;
@@ -18480,11 +18553,9 @@ class SubtitleStreamController extends BaseStreamController {
18480
18553
  }
18481
18554
  }
18482
18555
  loadFragment(frag, level, targetBufferTime) {
18483
- this.fragCurrent = frag;
18484
18556
  if (frag.sn === 'initSegment') {
18485
18557
  this._loadInitSegment(frag, level);
18486
18558
  } else {
18487
- this.startFragRequested = true;
18488
18559
  super.loadFragment(frag, level, targetBufferTime);
18489
18560
  }
18490
18561
  }
@@ -19149,6 +19220,7 @@ class BufferController extends Logger {
19149
19220
  hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
19150
19221
  hls.on(Events.FRAG_PARSED, this.onFragParsed, this);
19151
19222
  hls.on(Events.FRAG_CHANGED, this.onFragChanged, this);
19223
+ hls.on(Events.ERROR, this.onError, this);
19152
19224
  }
19153
19225
  unregisterListeners() {
19154
19226
  const {
@@ -19166,6 +19238,7 @@ class BufferController extends Logger {
19166
19238
  hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
19167
19239
  hls.off(Events.FRAG_PARSED, this.onFragParsed, this);
19168
19240
  hls.off(Events.FRAG_CHANGED, this.onFragChanged, this);
19241
+ hls.off(Events.ERROR, this.onError, this);
19169
19242
  }
19170
19243
  _initSourceBuffer() {
19171
19244
  this.sourceBuffer = {};
@@ -19175,11 +19248,7 @@ class BufferController extends Logger {
19175
19248
  video: [],
19176
19249
  audiovideo: []
19177
19250
  };
19178
- this.appendErrors = {
19179
- audio: 0,
19180
- video: 0,
19181
- audiovideo: 0
19182
- };
19251
+ this.resetAppendErrors();
19183
19252
  this.lastMpegAudioChunk = null;
19184
19253
  this.blockedAudioAppend = null;
19185
19254
  this.lastVideoAppendEnd = 0;
@@ -19716,6 +19785,22 @@ class BufferController extends Logger {
19716
19785
  this.updateMediaSource(durationAndRange);
19717
19786
  }
19718
19787
  }
19788
+ onError(event, data) {
19789
+ if (data.details === ErrorDetails.BUFFER_APPEND_ERROR && data.frag) {
19790
+ var _data$errorAction;
19791
+ const nextAutoLevel = (_data$errorAction = data.errorAction) == null ? void 0 : _data$errorAction.nextAutoLevel;
19792
+ if (isFiniteNumber(nextAutoLevel) && nextAutoLevel !== data.frag.level) {
19793
+ this.resetAppendErrors();
19794
+ }
19795
+ }
19796
+ }
19797
+ resetAppendErrors() {
19798
+ this.appendErrors = {
19799
+ audio: 0,
19800
+ video: 0,
19801
+ audiovideo: 0
19802
+ };
19803
+ }
19719
19804
  trimBuffers() {
19720
19805
  const {
19721
19806
  hls,
@@ -22542,7 +22627,7 @@ class TimelineController {
22542
22627
  const {
22543
22628
  unparsedVttFrags
22544
22629
  } = this;
22545
- if (id === 'main') {
22630
+ if (id === PlaylistLevelType.MAIN) {
22546
22631
  this.initPTS[frag.cc] = {
22547
22632
  baseTime: initPTS,
22548
22633
  timescale
@@ -28289,7 +28374,6 @@ class StreamController extends BaseStreamController {
28289
28374
  loadFragment(frag, level, targetBufferTime) {
28290
28375
  // Check if fragment is not loaded
28291
28376
  const fragState = this.fragmentTracker.getState(frag);
28292
- this.fragCurrent = frag;
28293
28377
  if (fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) {
28294
28378
  if (frag.sn === 'initSegment') {
28295
28379
  this._loadInitSegment(frag, level);
@@ -28297,7 +28381,6 @@ class StreamController extends BaseStreamController {
28297
28381
  this.log(`Fragment ${frag.sn} of level ${frag.level} is being downloaded to test bitrate and will not be buffered`);
28298
28382
  this._loadBitrateTestFrag(frag, level);
28299
28383
  } else {
28300
- this.startFragRequested = true;
28301
28384
  super.loadFragment(frag, level, targetBufferTime);
28302
28385
  }
28303
28386
  } else {
@@ -28431,14 +28514,14 @@ class StreamController extends BaseStreamController {
28431
28514
  super.onMediaDetaching();
28432
28515
  }
28433
28516
  onManifestLoading() {
28517
+ super.onManifestLoading();
28434
28518
  // reset buffer on manifest loading
28435
28519
  this.log('Trigger BUFFER_RESET');
28436
28520
  this.hls.trigger(Events.BUFFER_RESET, undefined);
28437
- this.fragmentTracker.removeAllFragments();
28438
28521
  this.couldBacktrack = false;
28439
- this.startPosition = this.lastCurrentTime = this.fragLastKbps = 0;
28440
- this.levels = this.fragPlaying = this.backtrackFragment = this.levelLastLoaded = null;
28441
- this.altAudio = this.audioOnly = this.startFragRequested = false;
28522
+ this.fragLastKbps = 0;
28523
+ this.fragPlaying = this.backtrackFragment = null;
28524
+ this.altAudio = this.audioOnly = false;
28442
28525
  }
28443
28526
  onManifestParsed(event, data) {
28444
28527
  // detect if we have different kind of audio codecs used amongst playlists
@@ -28734,7 +28817,7 @@ class StreamController extends BaseStreamController {
28734
28817
  // in that case, reset startFragRequested flag
28735
28818
  if (!this.loadedmetadata) {
28736
28819
  this.startFragRequested = false;
28737
- this.nextLoadPosition = this.startPosition;
28820
+ this.nextLoadPosition = this.lastCurrentTime;
28738
28821
  }
28739
28822
  this.tickImmediate();
28740
28823
  }
@@ -28822,7 +28905,7 @@ class StreamController extends BaseStreamController {
28822
28905
  }
28823
28906
  _handleTransmuxComplete(transmuxResult) {
28824
28907
  var _id3$samples;
28825
- const id = 'main';
28908
+ const id = this.playlistType;
28826
28909
  const {
28827
28910
  hls
28828
28911
  } = this;
@@ -29027,31 +29110,38 @@ class StreamController extends BaseStreamController {
29027
29110
  audio.levelCodec = audioCodec;
29028
29111
  audio.id = 'main';
29029
29112
  this.log(`Init audio buffer, container:${audio.container}, codecs[selected/level/parsed]=[${audioCodec || ''}/${currentLevel.audioCodec || ''}/${audio.codec}]`);
29113
+ delete tracks.audiovideo;
29030
29114
  }
29031
29115
  if (video) {
29032
29116
  video.levelCodec = currentLevel.videoCodec;
29033
29117
  video.id = 'main';
29034
29118
  this.log(`Init video buffer, container:${video.container}, codecs[level/parsed]=[${currentLevel.videoCodec || ''}/${video.codec}]`);
29119
+ delete tracks.audiovideo;
29035
29120
  }
29036
29121
  if (audiovideo) {
29037
29122
  this.log(`Init audiovideo buffer, container:${audiovideo.container}, codecs[level/parsed]=[${currentLevel.codecs}/${audiovideo.codec}]`);
29123
+ delete tracks.video;
29124
+ delete tracks.audio;
29125
+ }
29126
+ const trackTypes = Object.keys(tracks);
29127
+ if (trackTypes.length) {
29128
+ this.hls.trigger(Events.BUFFER_CODECS, tracks);
29129
+ // loop through tracks that are going to be provided to bufferController
29130
+ trackTypes.forEach(trackName => {
29131
+ const track = tracks[trackName];
29132
+ const initSegment = track.initSegment;
29133
+ if (initSegment != null && initSegment.byteLength) {
29134
+ this.hls.trigger(Events.BUFFER_APPENDING, {
29135
+ type: trackName,
29136
+ data: initSegment,
29137
+ frag,
29138
+ part: null,
29139
+ chunkMeta,
29140
+ parent: frag.type
29141
+ });
29142
+ }
29143
+ });
29038
29144
  }
29039
- this.hls.trigger(Events.BUFFER_CODECS, tracks);
29040
- // loop through tracks that are going to be provided to bufferController
29041
- Object.keys(tracks).forEach(trackName => {
29042
- const track = tracks[trackName];
29043
- const initSegment = track.initSegment;
29044
- if (initSegment != null && initSegment.byteLength) {
29045
- this.hls.trigger(Events.BUFFER_APPENDING, {
29046
- type: trackName,
29047
- data: initSegment,
29048
- frag,
29049
- part: null,
29050
- chunkMeta,
29051
- parent: frag.type
29052
- });
29053
- }
29054
- });
29055
29145
  // trigger handler right now
29056
29146
  this.tickImmediate();
29057
29147
  }
@@ -29128,20 +29218,28 @@ class StreamController extends BaseStreamController {
29128
29218
  return -1;
29129
29219
  }
29130
29220
  get currentFrag() {
29131
- const media = this.media;
29132
- if (media) {
29133
- return this.fragPlaying || this.getAppendedFrag(media.currentTime);
29221
+ var _this$media2;
29222
+ if (this.fragPlaying) {
29223
+ return this.fragPlaying;
29224
+ }
29225
+ const currentTime = ((_this$media2 = this.media) == null ? void 0 : _this$media2.currentTime) || this.lastCurrentTime;
29226
+ if (isFiniteNumber(currentTime)) {
29227
+ return this.getAppendedFrag(currentTime);
29134
29228
  }
29135
29229
  return null;
29136
29230
  }
29137
29231
  get currentProgramDateTime() {
29138
- const media = this.media;
29139
- if (media) {
29140
- const currentTime = media.currentTime;
29141
- const frag = this.currentFrag;
29142
- if (frag && isFiniteNumber(currentTime) && isFiniteNumber(frag.programDateTime)) {
29143
- const epocMs = frag.programDateTime + (currentTime - frag.start) * 1000;
29144
- return new Date(epocMs);
29232
+ var _this$media3;
29233
+ const currentTime = ((_this$media3 = this.media) == null ? void 0 : _this$media3.currentTime) || this.lastCurrentTime;
29234
+ if (isFiniteNumber(currentTime)) {
29235
+ const details = this.getLevelDetails();
29236
+ const frag = this.currentFrag || (details ? findFragmentByPTS(null, details.fragments, currentTime) : null);
29237
+ if (frag) {
29238
+ const programDateTime = frag.programDateTime;
29239
+ if (programDateTime !== null) {
29240
+ const epocMs = programDateTime + (currentTime - frag.start) * 1000;
29241
+ return new Date(epocMs);
29242
+ }
29145
29243
  }
29146
29244
  }
29147
29245
  return null;
@@ -29174,7 +29272,7 @@ class Hls {
29174
29272
  * Get the video-dev/hls.js package version.
29175
29273
  */
29176
29274
  static get version() {
29177
- return "1.5.12-0.canary.10366";
29275
+ return "1.5.12-0.canary.10367";
29178
29276
  }
29179
29277
 
29180
29278
  /**
@@ -29305,7 +29403,7 @@ class Hls {
29305
29403
  if (AudioStreamControllerClass) {
29306
29404
  networkControllers.push(new AudioStreamControllerClass(this, fragmentTracker, keyLoader));
29307
29405
  }
29308
- // subtitleTrackController must be defined before subtitleStreamController because the order of event handling is important
29406
+ // Instantiate subtitleTrackController before SubtitleStreamController to receive level events first
29309
29407
  this.subtitleTrackController = this.createController(config.subtitleTrackController, networkControllers);
29310
29408
  const SubtitleStreamControllerClass = config.subtitleStreamController;
29311
29409
  if (SubtitleStreamControllerClass) {