hls.js 1.6.0-beta.2.0.canary.10882 → 1.6.0-beta.2.0.canary.10884

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
@@ -77,6 +77,7 @@ let Events = /*#__PURE__*/function (Events) {
77
77
  Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
78
78
  Events["MEDIA_DETACHED"] = "hlsMediaDetached";
79
79
  Events["MEDIA_ENDED"] = "hlsMediaEnded";
80
+ Events["STALL_RESOLVED"] = "hlsStallResolved";
80
81
  Events["BUFFER_RESET"] = "hlsBufferReset";
81
82
  Events["BUFFER_CODECS"] = "hlsBufferCodecs";
82
83
  Events["BUFFER_CREATED"] = "hlsBufferCreated";
@@ -401,7 +402,7 @@ function enableLogs(debugConfig, context, id) {
401
402
  // Some browsers don't allow to use bind on console object anyway
402
403
  // fallback to default if needed
403
404
  try {
404
- newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.6.0-beta.2.0.canary.10882"}`);
405
+ newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.6.0-beta.2.0.canary.10884"}`);
405
406
  } catch (e) {
406
407
  /* log fn threw an exception. All logger methods are no-ops. */
407
408
  return createLogger();
@@ -5862,8 +5863,7 @@ class BufferHelper {
5862
5863
  return {
5863
5864
  len: 0,
5864
5865
  start: pos,
5865
- end: pos,
5866
- nextStart: undefined
5866
+ end: pos
5867
5867
  };
5868
5868
  }
5869
5869
  static bufferedInfo(buffered, pos, maxHoleDuration) {
@@ -5926,7 +5926,8 @@ class BufferHelper {
5926
5926
  len: bufferLen,
5927
5927
  start: bufferStart || 0,
5928
5928
  end: bufferEnd || 0,
5929
- nextStart: bufferStartNext
5929
+ nextStart: bufferStartNext,
5930
+ buffered
5930
5931
  };
5931
5932
  }
5932
5933
 
@@ -6294,8 +6295,6 @@ class LevelDetails {
6294
6295
  this.advancedDateTime = undefined;
6295
6296
  this.updated = true;
6296
6297
  this.advanced = true;
6297
- this.availabilityDelay = undefined;
6298
- // Manifest reload synchronization
6299
6298
  this.misses = 0;
6300
6299
  this.startCC = 0;
6301
6300
  this.startSN = 0;
@@ -6347,7 +6346,6 @@ class LevelDetails {
6347
6346
  } else {
6348
6347
  this.misses = previous.misses + 1;
6349
6348
  }
6350
- this.availabilityDelay = previous.availabilityDelay;
6351
6349
  }
6352
6350
  get hasProgramDateTime() {
6353
6351
  if (this.fragments.length) {
@@ -7303,7 +7301,7 @@ class M3U8Parser {
7303
7301
  if (!level.live) {
7304
7302
  lastFragment.endList = true;
7305
7303
  }
7306
- if (firstFragment && !level.startCC) {
7304
+ if (firstFragment && level.startCC === undefined) {
7307
7305
  level.startCC = firstFragment.cc;
7308
7306
  }
7309
7307
  /**
@@ -7586,15 +7584,16 @@ function mergeDetails(oldDetails, newDetails) {
7586
7584
  delete oldDetails.fragmentHint.endPTS;
7587
7585
  }
7588
7586
  // check if old/new playlists have fragments in common
7589
- // loop through overlapping SN and update startPTS , cc, and duration if any found
7590
- let ccOffset = 0;
7587
+ // loop through overlapping SN and update startPTS, cc, and duration if any found
7591
7588
  let PTSFrag;
7592
- mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag) => {
7593
- if (oldFrag.relurl) {
7594
- // Do not compare CC if the old fragment has no url. This is a level.fragmentHint used by LL-HLS parts.
7595
- // It maybe be off by 1 if it was created before any parts or discontinuity tags were appended to the end
7596
- // of the playlist.
7597
- ccOffset = oldFrag.cc - newFrag.cc;
7589
+ mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag, newFragIndex, newFragments) => {
7590
+ if (newDetails.skippedSegments) {
7591
+ if (newFrag.cc !== oldFrag.cc) {
7592
+ const ccOffset = oldFrag.cc - newFrag.cc;
7593
+ for (let i = newFragIndex; i < newFragments.length; i++) {
7594
+ newFragments[i].cc += ccOffset;
7595
+ }
7596
+ }
7598
7597
  }
7599
7598
  if (isFiniteNumber(oldFrag.startPTS) && isFiniteNumber(oldFrag.endPTS)) {
7600
7599
  newFrag.setStart(newFrag.startPTS = oldFrag.startPTS);
@@ -7623,7 +7622,8 @@ function mergeDetails(oldDetails, newDetails) {
7623
7622
  currentInitSegment = oldFrag.initSegment;
7624
7623
  }
7625
7624
  });
7626
- const fragmentsToCheck = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments;
7625
+ const newFragments = newDetails.fragments;
7626
+ const fragmentsToCheck = newDetails.fragmentHint ? newFragments.concat(newDetails.fragmentHint) : newFragments;
7627
7627
  if (currentInitSegment) {
7628
7628
  fragmentsToCheck.forEach(frag => {
7629
7629
  var _currentInitSegment;
@@ -7633,17 +7633,15 @@ function mergeDetails(oldDetails, newDetails) {
7633
7633
  });
7634
7634
  }
7635
7635
  if (newDetails.skippedSegments) {
7636
- newDetails.deltaUpdateFailed = newDetails.fragments.some(frag => !frag);
7636
+ newDetails.deltaUpdateFailed = newFragments.some(frag => !frag);
7637
7637
  if (newDetails.deltaUpdateFailed) {
7638
7638
  logger.warn('[level-helper] Previous playlist missing segments skipped in delta playlist');
7639
7639
  for (let i = newDetails.skippedSegments; i--;) {
7640
- newDetails.fragments.shift();
7641
- }
7642
- newDetails.startSN = newDetails.fragments[0].sn;
7643
- if (!newDetails.startCC) {
7644
- newDetails.startCC = newDetails.fragments[0].cc;
7640
+ newFragments.shift();
7645
7641
  }
7642
+ newDetails.startSN = newFragments[0].sn;
7646
7643
  } else {
7644
+ newDetails.endCC = newFragments[newFragments.length - 1].cc;
7647
7645
  if (newDetails.canSkipDateRanges) {
7648
7646
  newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails);
7649
7647
  }
@@ -7658,16 +7656,6 @@ function mergeDetails(oldDetails, newDetails) {
7658
7656
  mapDateRanges(programDateTimes, newDetails);
7659
7657
  }
7660
7658
  }
7661
- const newFragments = newDetails.fragments;
7662
- if (ccOffset) {
7663
- logger.warn('discontinuity sliding from playlist, take drift into account');
7664
- for (let i = 0; i < newFragments.length; i++) {
7665
- newFragments[i].cc += ccOffset;
7666
- }
7667
- }
7668
- if (newDetails.skippedSegments) {
7669
- newDetails.startCC = newDetails.fragments[0].cc;
7670
- }
7671
7659
 
7672
7660
  // Merge parts
7673
7661
  mapPartIntersection(oldDetails.partList, newDetails.partList, (oldPart, newPart) => {
@@ -7765,7 +7753,7 @@ function mapFragmentIntersection(oldDetails, newDetails, intersectionFn) {
7765
7753
  newFrag = newDetails.fragments[i] = oldFrag;
7766
7754
  }
7767
7755
  if (oldFrag && newFrag) {
7768
- intersectionFn(oldFrag, newFrag);
7756
+ intersectionFn(oldFrag, newFrag, i, newFrags);
7769
7757
  }
7770
7758
  }
7771
7759
  }
@@ -9847,7 +9835,7 @@ var eventemitter3 = {exports: {}};
9847
9835
  var eventemitter3Exports = eventemitter3.exports;
9848
9836
  var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports);
9849
9837
 
9850
- const version = "1.6.0-beta.2.0.canary.10882";
9838
+ const version = "1.6.0-beta.2.0.canary.10884";
9851
9839
 
9852
9840
  // ensure the worker ends up in the bundle
9853
9841
  // If the worker should not be included this gets aliased to empty.js
@@ -29443,6 +29431,7 @@ const hlsDefaultConfig = _objectSpread2(_objectSpread2({
29443
29431
  progressive: false,
29444
29432
  lowLatencyMode: true,
29445
29433
  cmcd: undefined,
29434
+ detectStallWithCurrentTimeMs: 1250,
29446
29435
  enableDateRangeMetadataCues: true,
29447
29436
  enableEmsgMetadataCues: true,
29448
29437
  enableEmsgKLVMetadata: false,
@@ -30851,32 +30840,28 @@ function assignTrackIdsByGroup(tracks) {
30851
30840
  });
30852
30841
  }
30853
30842
 
30854
- const STALL_MINIMUM_DURATION_MS = 250;
30855
30843
  const MAX_START_GAP_JUMP = 2.0;
30856
30844
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
30857
30845
  const SKIP_BUFFER_RANGE_START = 0.05;
30858
30846
  class GapController extends Logger {
30859
- constructor(config, media, fragmentTracker, hls) {
30847
+ constructor(media, fragmentTracker, hls) {
30860
30848
  super('gap-controller', hls.logger);
30861
- this.config = undefined;
30862
30849
  this.media = null;
30863
- this.fragmentTracker = undefined;
30864
- this.hls = undefined;
30850
+ this.fragmentTracker = null;
30851
+ this.hls = null;
30865
30852
  this.nudgeRetry = 0;
30866
30853
  this.stallReported = false;
30867
30854
  this.stalled = null;
30868
30855
  this.moved = false;
30869
30856
  this.seeking = false;
30870
30857
  this.ended = 0;
30871
- this.config = config;
30858
+ this.waiting = 0;
30872
30859
  this.media = media;
30873
30860
  this.fragmentTracker = fragmentTracker;
30874
30861
  this.hls = hls;
30875
30862
  }
30876
30863
  destroy() {
30877
- this.media = null;
30878
- // @ts-ignore
30879
- this.hls = this.fragmentTracker = null;
30864
+ this.media = this.hls = this.fragmentTracker = null;
30880
30865
  }
30881
30866
 
30882
30867
  /**
@@ -30886,12 +30871,12 @@ class GapController extends Logger {
30886
30871
  * @param lastCurrentTime - Previously read playhead position
30887
30872
  */
30888
30873
  poll(lastCurrentTime, activeFrag, levelDetails, state) {
30874
+ var _this$hls;
30889
30875
  const {
30890
- config,
30891
30876
  media,
30892
30877
  stalled
30893
30878
  } = this;
30894
- if (media === null) {
30879
+ if (!media) {
30895
30880
  return;
30896
30881
  }
30897
30882
  const {
@@ -30911,43 +30896,45 @@ class GapController extends Logger {
30911
30896
  if (!seeking) {
30912
30897
  this.nudgeRetry = 0;
30913
30898
  }
30914
- if (stalled !== null) {
30915
- // The playhead is now moving, but was previously stalled
30916
- if (this.stallReported) {
30917
- const _stalledDuration = self.performance.now() - stalled;
30918
- this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
30919
- this.stallReported = false;
30920
- }
30921
- this.stalled = null;
30899
+ if (this.waiting === 0) {
30900
+ this.stallResolved(currentTime);
30922
30901
  }
30923
30902
  return;
30924
30903
  }
30925
30904
 
30926
30905
  // Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek
30927
30906
  if (beginSeek || seeked) {
30928
- this.stalled = null;
30907
+ if (seeked) {
30908
+ this.stallResolved(currentTime);
30909
+ }
30929
30910
  return;
30930
30911
  }
30931
30912
 
30932
30913
  // The playhead should not be moving
30933
- if (media.paused && !seeking || media.ended || media.playbackRate === 0 || !BufferHelper.getBuffered(media).length) {
30914
+ if (media.paused && !seeking || media.ended || media.playbackRate === 0) {
30915
+ this.nudgeRetry = 0;
30916
+ this.stallResolved(currentTime);
30934
30917
  // Fire MEDIA_ENDED to workaround event not being dispatched by browser
30935
- if (!this.ended && media.ended) {
30918
+ if (!this.ended && media.ended && this.hls) {
30936
30919
  this.ended = currentTime || 1;
30937
30920
  this.hls.trigger(Events.MEDIA_ENDED, {
30938
30921
  stalled: false
30939
30922
  });
30940
30923
  }
30924
+ return;
30925
+ }
30926
+ if (!BufferHelper.getBuffered(media).length) {
30941
30927
  this.nudgeRetry = 0;
30942
30928
  return;
30943
30929
  }
30944
30930
  const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
30945
30931
  const nextStart = bufferInfo.nextStart || 0;
30946
- if (seeking) {
30932
+ const fragmentTracker = this.fragmentTracker;
30933
+ if (seeking && fragmentTracker) {
30947
30934
  // Waiting for seeking in a buffered range to complete
30948
30935
  const hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
30949
30936
  // Next buffered range is too far ahead to jump to while still seeking
30950
- const noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !this.fragmentTracker.getPartialFragment(currentTime);
30937
+ const noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !fragmentTracker.getPartialFragment(currentTime);
30951
30938
  if (hasEnoughBuffer || noBufferGap) {
30952
30939
  return;
30953
30940
  }
@@ -30957,7 +30944,7 @@ class GapController extends Logger {
30957
30944
 
30958
30945
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
30959
30946
  // The addition poll gives the browser a chance to jump the gap for us
30960
- if (!this.moved && this.stalled !== null) {
30947
+ if (!this.moved && this.stalled !== null && fragmentTracker) {
30961
30948
  // There is no playable buffer (seeked, waiting for buffer)
30962
30949
  const isBuffered = bufferInfo.len > 0;
30963
30950
  if (!isBuffered && !nextStart) {
@@ -30971,7 +30958,7 @@ class GapController extends Logger {
30971
30958
  // that begins over 1 target duration after the video start position.
30972
30959
  const isLive = !!(levelDetails != null && levelDetails.live);
30973
30960
  const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
30974
- const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
30961
+ const partialOrGap = fragmentTracker.getPartialFragment(currentTime);
30975
30962
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
30976
30963
  if (!media.paused) {
30977
30964
  this._trySkipBufferHole(partialOrGap);
@@ -30981,16 +30968,27 @@ class GapController extends Logger {
30981
30968
  }
30982
30969
 
30983
30970
  // Start tracking stall time
30971
+ const config = (_this$hls = this.hls) == null ? undefined : _this$hls.config;
30972
+ if (!config) {
30973
+ return;
30974
+ }
30975
+ const detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs;
30984
30976
  const tnow = self.performance.now();
30977
+ const tWaiting = this.waiting;
30985
30978
  if (stalled === null) {
30986
- this.stalled = tnow;
30979
+ // Use time of recent "waiting" event
30980
+ if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) {
30981
+ this.stalled = tWaiting;
30982
+ } else {
30983
+ this.stalled = tnow;
30984
+ }
30987
30985
  return;
30988
30986
  }
30989
30987
  const stalledDuration = tnow - stalled;
30990
- if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
30988
+ if (!seeking && (stalledDuration >= detectStallWithCurrentTimeMs || tWaiting) && this.hls) {
30991
30989
  // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
30992
30990
  if (state === State.ENDED && !(levelDetails != null && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? undefined : levelDetails.edge) || 0)) < 1) {
30993
- if (stalledDuration < 1000 || this.ended) {
30991
+ if (this.ended) {
30994
30992
  return;
30995
30993
  }
30996
30994
  this.ended = currentTime || 1;
@@ -31001,13 +30999,27 @@ class GapController extends Logger {
31001
30999
  }
31002
31000
  // Report stalling after trying to fix
31003
31001
  this._reportStall(bufferInfo);
31004
- if (!this.media) {
31002
+ if (!this.media || !this.hls) {
31005
31003
  return;
31006
31004
  }
31007
31005
  }
31008
31006
  const bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole);
31009
31007
  this._tryFixBufferStall(bufferedWithHoles, stalledDuration);
31010
31008
  }
31009
+ stallResolved(currentTime) {
31010
+ const stalled = this.stalled;
31011
+ if (stalled && this.hls) {
31012
+ this.stalled = null;
31013
+ // The playhead is now moving, but was previously stalled
31014
+ if (this.stallReported) {
31015
+ const stalledDuration = self.performance.now() - stalled;
31016
+ this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(stalledDuration)}ms`);
31017
+ this.stallReported = false;
31018
+ this.waiting = 0;
31019
+ this.hls.trigger(Events.STALL_RESOLVED, {});
31020
+ }
31021
+ }
31022
+ }
31011
31023
 
31012
31024
  /**
31013
31025
  * Detects and attempts to fix known buffer stalling issues.
@@ -31016,12 +31028,13 @@ class GapController extends Logger {
31016
31028
  * @private
31017
31029
  */
31018
31030
  _tryFixBufferStall(bufferInfo, stalledDurationMs) {
31031
+ var _this$hls2;
31019
31032
  const {
31020
- config,
31021
31033
  fragmentTracker,
31022
31034
  media
31023
31035
  } = this;
31024
- if (media === null) {
31036
+ const config = (_this$hls2 = this.hls) == null ? undefined : _this$hls2.config;
31037
+ if (!media || !fragmentTracker || !config) {
31025
31038
  return;
31026
31039
  }
31027
31040
  const currentTime = media.currentTime;
@@ -31041,13 +31054,12 @@ class GapController extends Logger {
31041
31054
  // we may just have to "nudge" the playlist as the browser decoding/rendering engine
31042
31055
  // needs to cross some sort of threshold covering all source-buffers content
31043
31056
  // to start playing properly.
31044
- if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
31057
+ const bufferedRanges = bufferInfo.buffered;
31058
+ if ((bufferedRanges && bufferedRanges.length > 1 && bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
31045
31059
  this.warn('Trying to nudge playhead over buffer-hole');
31046
31060
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
31047
31061
  // We only try to jump the hole if it's under the configured size
31048
- // Reset stalled so to rearm watchdog timer
31049
- this.stalled = null;
31050
- this._tryNudgeBuffer();
31062
+ this._tryNudgeBuffer(bufferInfo);
31051
31063
  }
31052
31064
  }
31053
31065
 
@@ -31060,9 +31072,10 @@ class GapController extends Logger {
31060
31072
  const {
31061
31073
  hls,
31062
31074
  media,
31063
- stallReported
31075
+ stallReported,
31076
+ stalled
31064
31077
  } = this;
31065
- if (!stallReported && media) {
31078
+ if (!stallReported && stalled !== null && media && hls) {
31066
31079
  // Report stalled error once
31067
31080
  this.stallReported = true;
31068
31081
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
@@ -31072,7 +31085,11 @@ class GapController extends Logger {
31072
31085
  details: ErrorDetails.BUFFER_STALLED_ERROR,
31073
31086
  fatal: false,
31074
31087
  error,
31075
- buffer: bufferInfo.len
31088
+ buffer: bufferInfo.len,
31089
+ bufferInfo,
31090
+ stalled: {
31091
+ start: stalled
31092
+ }
31076
31093
  });
31077
31094
  }
31078
31095
  }
@@ -31083,12 +31100,13 @@ class GapController extends Logger {
31083
31100
  * @private
31084
31101
  */
31085
31102
  _trySkipBufferHole(partial) {
31103
+ var _this$hls3;
31086
31104
  const {
31087
- config,
31088
- hls,
31105
+ fragmentTracker,
31089
31106
  media
31090
31107
  } = this;
31091
- if (media === null) {
31108
+ const config = (_this$hls3 = this.hls) == null ? undefined : _this$hls3.config;
31109
+ if (!media || !fragmentTracker || !config) {
31092
31110
  return 0;
31093
31111
  }
31094
31112
 
@@ -31103,9 +31121,6 @@ class GapController extends Logger {
31103
31121
  if (gapLength > 0 && (bufferStarved || waiting)) {
31104
31122
  // Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial
31105
31123
  if (gapLength > config.maxBufferHole) {
31106
- const {
31107
- fragmentTracker
31108
- } = this;
31109
31124
  let startGap = false;
31110
31125
  if (currentTime === 0) {
31111
31126
  const startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN);
@@ -31136,17 +31151,18 @@ class GapController extends Logger {
31136
31151
  const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
31137
31152
  this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
31138
31153
  this.moved = true;
31139
- this.stalled = null;
31140
31154
  media.currentTime = targetTime;
31141
- if (partial && !partial.gap) {
31155
+ if (partial && !partial.gap && this.hls) {
31142
31156
  const error = new Error(`fragment loaded with buffer holes, seeking from ${currentTime} to ${targetTime}`);
31143
- hls.trigger(Events.ERROR, {
31157
+ this.hls.trigger(Events.ERROR, {
31144
31158
  type: ErrorTypes.MEDIA_ERROR,
31145
31159
  details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
31146
31160
  fatal: false,
31147
31161
  error,
31148
31162
  reason: error.message,
31149
- frag: partial
31163
+ frag: partial,
31164
+ buffer: bufferInfo.len,
31165
+ bufferInfo
31150
31166
  });
31151
31167
  }
31152
31168
  return targetTime;
@@ -31159,15 +31175,15 @@ class GapController extends Logger {
31159
31175
  * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount.
31160
31176
  * @private
31161
31177
  */
31162
- _tryNudgeBuffer() {
31178
+ _tryNudgeBuffer(bufferInfo) {
31163
31179
  const {
31164
- config,
31165
31180
  hls,
31166
31181
  media,
31167
31182
  nudgeRetry
31168
31183
  } = this;
31169
- if (media === null) {
31170
- return;
31184
+ const config = hls == null ? undefined : hls.config;
31185
+ if (!media || !config) {
31186
+ return 0;
31171
31187
  }
31172
31188
  const currentTime = media.currentTime;
31173
31189
  this.nudgeRetry++;
@@ -31181,7 +31197,9 @@ class GapController extends Logger {
31181
31197
  type: ErrorTypes.MEDIA_ERROR,
31182
31198
  details: ErrorDetails.BUFFER_NUDGE_ON_STALL,
31183
31199
  error,
31184
- fatal: false
31200
+ fatal: false,
31201
+ buffer: bufferInfo.len,
31202
+ bufferInfo
31185
31203
  });
31186
31204
  } else {
31187
31205
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
@@ -31190,7 +31208,9 @@ class GapController extends Logger {
31190
31208
  type: ErrorTypes.MEDIA_ERROR,
31191
31209
  details: ErrorDetails.BUFFER_STALLED_ERROR,
31192
31210
  error,
31193
- fatal: true
31211
+ fatal: true,
31212
+ buffer: bufferInfo.len,
31213
+ bufferInfo
31194
31214
  });
31195
31215
  }
31196
31216
  }
@@ -31241,11 +31261,18 @@ class StreamController extends BaseStreamController {
31241
31261
  this.backtrackFragment = null;
31242
31262
  this.audioCodecSwitch = false;
31243
31263
  this.videoBuffer = null;
31264
+ this.onMediaWaiting = () => {
31265
+ const gapController = this.gapController;
31266
+ if (gapController) {
31267
+ gapController.waiting = self.performance.now();
31268
+ }
31269
+ };
31244
31270
  this.onMediaPlaying = () => {
31245
31271
  // tick to speed up FRAG_CHANGED triggering
31246
31272
  const gapController = this.gapController;
31247
31273
  if (gapController) {
31248
31274
  gapController.ended = 0;
31275
+ gapController.waiting = 0;
31249
31276
  }
31250
31277
  this.tick();
31251
31278
  };
@@ -31301,7 +31328,7 @@ class StreamController extends BaseStreamController {
31301
31328
  }
31302
31329
  onHandlerDestroying() {
31303
31330
  // @ts-ignore
31304
- this.onMediaPlaying = this.onMediaSeeked = null;
31331
+ this.onMediaPlaying = this.onMediaSeeked = this.onMediaWaiting = null;
31305
31332
  this.unregisterListeners();
31306
31333
  super.onHandlerDestroying();
31307
31334
  }
@@ -31632,9 +31659,11 @@ class StreamController extends BaseStreamController {
31632
31659
  const media = data.media;
31633
31660
  media.removeEventListener('playing', this.onMediaPlaying);
31634
31661
  media.removeEventListener('seeked', this.onMediaSeeked);
31662
+ media.removeEventListener('waiting', this.onMediaWaiting);
31635
31663
  media.addEventListener('playing', this.onMediaPlaying);
31636
31664
  media.addEventListener('seeked', this.onMediaSeeked);
31637
- this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
31665
+ media.addEventListener('waiting', this.onMediaWaiting);
31666
+ this.gapController = new GapController(media, this.fragmentTracker, this.hls);
31638
31667
  }
31639
31668
  onMediaDetaching(event, data) {
31640
31669
  const {
@@ -31643,6 +31672,7 @@ class StreamController extends BaseStreamController {
31643
31672
  if (media) {
31644
31673
  media.removeEventListener('playing', this.onMediaPlaying);
31645
31674
  media.removeEventListener('seeked', this.onMediaSeeked);
31675
+ media.removeEventListener('waiting', this.onMediaWaiting);
31646
31676
  }
31647
31677
  this.videoBuffer = null;
31648
31678
  this.fragPlaying = null;
@@ -32070,7 +32100,7 @@ class StreamController extends BaseStreamController {
32070
32100
  let startPosition = this.startPosition;
32071
32101
  // only adjust currentTime if different from startPosition or if startPosition not buffered
32072
32102
  // at that stage, there should be only one buffered range, as we reach that code after first fragment has been buffered
32073
- if (startPosition >= 0) {
32103
+ if (startPosition >= 0 && currentTime < startPosition) {
32074
32104
  if (media.seeking) {
32075
32105
  this.log(`could not seek to ${startPosition}, already seeking at ${currentTime}`);
32076
32106
  return;