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.
@@ -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();
@@ -3150,8 +3151,6 @@ class LevelDetails {
3150
3151
  this.advancedDateTime = undefined;
3151
3152
  this.updated = true;
3152
3153
  this.advanced = true;
3153
- this.availabilityDelay = undefined;
3154
- // Manifest reload synchronization
3155
3154
  this.misses = 0;
3156
3155
  this.startCC = 0;
3157
3156
  this.startSN = 0;
@@ -3203,7 +3202,6 @@ class LevelDetails {
3203
3202
  } else {
3204
3203
  this.misses = previous.misses + 1;
3205
3204
  }
3206
- this.availabilityDelay = previous.availabilityDelay;
3207
3205
  }
3208
3206
  get hasProgramDateTime() {
3209
3207
  if (this.fragments.length) {
@@ -5041,7 +5039,7 @@ class M3U8Parser {
5041
5039
  if (!level.live) {
5042
5040
  lastFragment.endList = true;
5043
5041
  }
5044
- if (firstFragment && !level.startCC) {
5042
+ if (firstFragment && level.startCC === undefined) {
5045
5043
  level.startCC = firstFragment.cc;
5046
5044
  }
5047
5045
  /**
@@ -5324,15 +5322,16 @@ function mergeDetails(oldDetails, newDetails) {
5324
5322
  delete oldDetails.fragmentHint.endPTS;
5325
5323
  }
5326
5324
  // check if old/new playlists have fragments in common
5327
- // loop through overlapping SN and update startPTS , cc, and duration if any found
5328
- let ccOffset = 0;
5325
+ // loop through overlapping SN and update startPTS, cc, and duration if any found
5329
5326
  let PTSFrag;
5330
- mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag) => {
5331
- if (oldFrag.relurl) {
5332
- // Do not compare CC if the old fragment has no url. This is a level.fragmentHint used by LL-HLS parts.
5333
- // It maybe be off by 1 if it was created before any parts or discontinuity tags were appended to the end
5334
- // of the playlist.
5335
- ccOffset = oldFrag.cc - newFrag.cc;
5327
+ mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag, newFragIndex, newFragments) => {
5328
+ if (newDetails.skippedSegments) {
5329
+ if (newFrag.cc !== oldFrag.cc) {
5330
+ const ccOffset = oldFrag.cc - newFrag.cc;
5331
+ for (let i = newFragIndex; i < newFragments.length; i++) {
5332
+ newFragments[i].cc += ccOffset;
5333
+ }
5334
+ }
5336
5335
  }
5337
5336
  if (isFiniteNumber(oldFrag.startPTS) && isFiniteNumber(oldFrag.endPTS)) {
5338
5337
  newFrag.setStart(newFrag.startPTS = oldFrag.startPTS);
@@ -5361,7 +5360,8 @@ function mergeDetails(oldDetails, newDetails) {
5361
5360
  currentInitSegment = oldFrag.initSegment;
5362
5361
  }
5363
5362
  });
5364
- const fragmentsToCheck = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments;
5363
+ const newFragments = newDetails.fragments;
5364
+ const fragmentsToCheck = newDetails.fragmentHint ? newFragments.concat(newDetails.fragmentHint) : newFragments;
5365
5365
  if (currentInitSegment) {
5366
5366
  fragmentsToCheck.forEach(frag => {
5367
5367
  var _currentInitSegment;
@@ -5371,17 +5371,15 @@ function mergeDetails(oldDetails, newDetails) {
5371
5371
  });
5372
5372
  }
5373
5373
  if (newDetails.skippedSegments) {
5374
- newDetails.deltaUpdateFailed = newDetails.fragments.some(frag => !frag);
5374
+ newDetails.deltaUpdateFailed = newFragments.some(frag => !frag);
5375
5375
  if (newDetails.deltaUpdateFailed) {
5376
5376
  logger.warn('[level-helper] Previous playlist missing segments skipped in delta playlist');
5377
5377
  for (let i = newDetails.skippedSegments; i--;) {
5378
- newDetails.fragments.shift();
5379
- }
5380
- newDetails.startSN = newDetails.fragments[0].sn;
5381
- if (!newDetails.startCC) {
5382
- newDetails.startCC = newDetails.fragments[0].cc;
5378
+ newFragments.shift();
5383
5379
  }
5380
+ newDetails.startSN = newFragments[0].sn;
5384
5381
  } else {
5382
+ newDetails.endCC = newFragments[newFragments.length - 1].cc;
5385
5383
  if (newDetails.canSkipDateRanges) {
5386
5384
  newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails);
5387
5385
  }
@@ -5396,16 +5394,6 @@ function mergeDetails(oldDetails, newDetails) {
5396
5394
  mapDateRanges(programDateTimes, newDetails);
5397
5395
  }
5398
5396
  }
5399
- const newFragments = newDetails.fragments;
5400
- if (ccOffset) {
5401
- logger.warn('discontinuity sliding from playlist, take drift into account');
5402
- for (let i = 0; i < newFragments.length; i++) {
5403
- newFragments[i].cc += ccOffset;
5404
- }
5405
- }
5406
- if (newDetails.skippedSegments) {
5407
- newDetails.startCC = newDetails.fragments[0].cc;
5408
- }
5409
5397
 
5410
5398
  // Merge parts
5411
5399
  mapPartIntersection(oldDetails.partList, newDetails.partList, (oldPart, newPart) => {
@@ -5503,7 +5491,7 @@ function mapFragmentIntersection(oldDetails, newDetails, intersectionFn) {
5503
5491
  newFrag = newDetails.fragments[i] = oldFrag;
5504
5492
  }
5505
5493
  if (oldFrag && newFrag) {
5506
- intersectionFn(oldFrag, newFrag);
5494
+ intersectionFn(oldFrag, newFrag, i, newFrags);
5507
5495
  }
5508
5496
  }
5509
5497
  }
@@ -7283,8 +7271,7 @@ class BufferHelper {
7283
7271
  return {
7284
7272
  len: 0,
7285
7273
  start: pos,
7286
- end: pos,
7287
- nextStart: undefined
7274
+ end: pos
7288
7275
  };
7289
7276
  }
7290
7277
  static bufferedInfo(buffered, pos, maxHoleDuration) {
@@ -7347,7 +7334,8 @@ class BufferHelper {
7347
7334
  len: bufferLen,
7348
7335
  start: bufferStart || 0,
7349
7336
  end: bufferEnd || 0,
7350
- nextStart: bufferStartNext
7337
+ nextStart: bufferStartNext,
7338
+ buffered
7351
7339
  };
7352
7340
  }
7353
7341
 
@@ -17283,6 +17271,7 @@ const hlsDefaultConfig = _objectSpread2(_objectSpread2({
17283
17271
  progressive: false,
17284
17272
  lowLatencyMode: true,
17285
17273
  cmcd: undefined,
17274
+ detectStallWithCurrentTimeMs: 1250,
17286
17275
  enableDateRangeMetadataCues: true,
17287
17276
  enableEmsgMetadataCues: true,
17288
17277
  enableEmsgKLVMetadata: false,
@@ -18768,32 +18757,28 @@ function assignTrackIdsByGroup(tracks) {
18768
18757
  });
18769
18758
  }
18770
18759
 
18771
- const STALL_MINIMUM_DURATION_MS = 250;
18772
18760
  const MAX_START_GAP_JUMP = 2.0;
18773
18761
  const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
18774
18762
  const SKIP_BUFFER_RANGE_START = 0.05;
18775
18763
  class GapController extends Logger {
18776
- constructor(config, media, fragmentTracker, hls) {
18764
+ constructor(media, fragmentTracker, hls) {
18777
18765
  super('gap-controller', hls.logger);
18778
- this.config = undefined;
18779
18766
  this.media = null;
18780
- this.fragmentTracker = undefined;
18781
- this.hls = undefined;
18767
+ this.fragmentTracker = null;
18768
+ this.hls = null;
18782
18769
  this.nudgeRetry = 0;
18783
18770
  this.stallReported = false;
18784
18771
  this.stalled = null;
18785
18772
  this.moved = false;
18786
18773
  this.seeking = false;
18787
18774
  this.ended = 0;
18788
- this.config = config;
18775
+ this.waiting = 0;
18789
18776
  this.media = media;
18790
18777
  this.fragmentTracker = fragmentTracker;
18791
18778
  this.hls = hls;
18792
18779
  }
18793
18780
  destroy() {
18794
- this.media = null;
18795
- // @ts-ignore
18796
- this.hls = this.fragmentTracker = null;
18781
+ this.media = this.hls = this.fragmentTracker = null;
18797
18782
  }
18798
18783
 
18799
18784
  /**
@@ -18803,12 +18788,12 @@ class GapController extends Logger {
18803
18788
  * @param lastCurrentTime - Previously read playhead position
18804
18789
  */
18805
18790
  poll(lastCurrentTime, activeFrag, levelDetails, state) {
18791
+ var _this$hls;
18806
18792
  const {
18807
- config,
18808
18793
  media,
18809
18794
  stalled
18810
18795
  } = this;
18811
- if (media === null) {
18796
+ if (!media) {
18812
18797
  return;
18813
18798
  }
18814
18799
  const {
@@ -18828,43 +18813,45 @@ class GapController extends Logger {
18828
18813
  if (!seeking) {
18829
18814
  this.nudgeRetry = 0;
18830
18815
  }
18831
- if (stalled !== null) {
18832
- // The playhead is now moving, but was previously stalled
18833
- if (this.stallReported) {
18834
- const _stalledDuration = self.performance.now() - stalled;
18835
- this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
18836
- this.stallReported = false;
18837
- }
18838
- this.stalled = null;
18816
+ if (this.waiting === 0) {
18817
+ this.stallResolved(currentTime);
18839
18818
  }
18840
18819
  return;
18841
18820
  }
18842
18821
 
18843
18822
  // Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek
18844
18823
  if (beginSeek || seeked) {
18845
- this.stalled = null;
18824
+ if (seeked) {
18825
+ this.stallResolved(currentTime);
18826
+ }
18846
18827
  return;
18847
18828
  }
18848
18829
 
18849
18830
  // The playhead should not be moving
18850
- if (media.paused && !seeking || media.ended || media.playbackRate === 0 || !BufferHelper.getBuffered(media).length) {
18831
+ if (media.paused && !seeking || media.ended || media.playbackRate === 0) {
18832
+ this.nudgeRetry = 0;
18833
+ this.stallResolved(currentTime);
18851
18834
  // Fire MEDIA_ENDED to workaround event not being dispatched by browser
18852
- if (!this.ended && media.ended) {
18835
+ if (!this.ended && media.ended && this.hls) {
18853
18836
  this.ended = currentTime || 1;
18854
18837
  this.hls.trigger(Events.MEDIA_ENDED, {
18855
18838
  stalled: false
18856
18839
  });
18857
18840
  }
18841
+ return;
18842
+ }
18843
+ if (!BufferHelper.getBuffered(media).length) {
18858
18844
  this.nudgeRetry = 0;
18859
18845
  return;
18860
18846
  }
18861
18847
  const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
18862
18848
  const nextStart = bufferInfo.nextStart || 0;
18863
- if (seeking) {
18849
+ const fragmentTracker = this.fragmentTracker;
18850
+ if (seeking && fragmentTracker) {
18864
18851
  // Waiting for seeking in a buffered range to complete
18865
18852
  const hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
18866
18853
  // Next buffered range is too far ahead to jump to while still seeking
18867
- const noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !this.fragmentTracker.getPartialFragment(currentTime);
18854
+ const noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !fragmentTracker.getPartialFragment(currentTime);
18868
18855
  if (hasEnoughBuffer || noBufferGap) {
18869
18856
  return;
18870
18857
  }
@@ -18874,7 +18861,7 @@ class GapController extends Logger {
18874
18861
 
18875
18862
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
18876
18863
  // The addition poll gives the browser a chance to jump the gap for us
18877
- if (!this.moved && this.stalled !== null) {
18864
+ if (!this.moved && this.stalled !== null && fragmentTracker) {
18878
18865
  // There is no playable buffer (seeked, waiting for buffer)
18879
18866
  const isBuffered = bufferInfo.len > 0;
18880
18867
  if (!isBuffered && !nextStart) {
@@ -18888,7 +18875,7 @@ class GapController extends Logger {
18888
18875
  // that begins over 1 target duration after the video start position.
18889
18876
  const isLive = !!(levelDetails != null && levelDetails.live);
18890
18877
  const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
18891
- const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
18878
+ const partialOrGap = fragmentTracker.getPartialFragment(currentTime);
18892
18879
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
18893
18880
  if (!media.paused) {
18894
18881
  this._trySkipBufferHole(partialOrGap);
@@ -18898,16 +18885,27 @@ class GapController extends Logger {
18898
18885
  }
18899
18886
 
18900
18887
  // Start tracking stall time
18888
+ const config = (_this$hls = this.hls) == null ? undefined : _this$hls.config;
18889
+ if (!config) {
18890
+ return;
18891
+ }
18892
+ const detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs;
18901
18893
  const tnow = self.performance.now();
18894
+ const tWaiting = this.waiting;
18902
18895
  if (stalled === null) {
18903
- this.stalled = tnow;
18896
+ // Use time of recent "waiting" event
18897
+ if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) {
18898
+ this.stalled = tWaiting;
18899
+ } else {
18900
+ this.stalled = tnow;
18901
+ }
18904
18902
  return;
18905
18903
  }
18906
18904
  const stalledDuration = tnow - stalled;
18907
- if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
18905
+ if (!seeking && (stalledDuration >= detectStallWithCurrentTimeMs || tWaiting) && this.hls) {
18908
18906
  // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
18909
18907
  if (state === State.ENDED && !(levelDetails != null && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? undefined : levelDetails.edge) || 0)) < 1) {
18910
- if (stalledDuration < 1000 || this.ended) {
18908
+ if (this.ended) {
18911
18909
  return;
18912
18910
  }
18913
18911
  this.ended = currentTime || 1;
@@ -18918,13 +18916,27 @@ class GapController extends Logger {
18918
18916
  }
18919
18917
  // Report stalling after trying to fix
18920
18918
  this._reportStall(bufferInfo);
18921
- if (!this.media) {
18919
+ if (!this.media || !this.hls) {
18922
18920
  return;
18923
18921
  }
18924
18922
  }
18925
18923
  const bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole);
18926
18924
  this._tryFixBufferStall(bufferedWithHoles, stalledDuration);
18927
18925
  }
18926
+ stallResolved(currentTime) {
18927
+ const stalled = this.stalled;
18928
+ if (stalled && this.hls) {
18929
+ this.stalled = null;
18930
+ // The playhead is now moving, but was previously stalled
18931
+ if (this.stallReported) {
18932
+ const stalledDuration = self.performance.now() - stalled;
18933
+ this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(stalledDuration)}ms`);
18934
+ this.stallReported = false;
18935
+ this.waiting = 0;
18936
+ this.hls.trigger(Events.STALL_RESOLVED, {});
18937
+ }
18938
+ }
18939
+ }
18928
18940
 
18929
18941
  /**
18930
18942
  * Detects and attempts to fix known buffer stalling issues.
@@ -18933,12 +18945,13 @@ class GapController extends Logger {
18933
18945
  * @private
18934
18946
  */
18935
18947
  _tryFixBufferStall(bufferInfo, stalledDurationMs) {
18948
+ var _this$hls2;
18936
18949
  const {
18937
- config,
18938
18950
  fragmentTracker,
18939
18951
  media
18940
18952
  } = this;
18941
- if (media === null) {
18953
+ const config = (_this$hls2 = this.hls) == null ? undefined : _this$hls2.config;
18954
+ if (!media || !fragmentTracker || !config) {
18942
18955
  return;
18943
18956
  }
18944
18957
  const currentTime = media.currentTime;
@@ -18958,13 +18971,12 @@ class GapController extends Logger {
18958
18971
  // we may just have to "nudge" the playlist as the browser decoding/rendering engine
18959
18972
  // needs to cross some sort of threshold covering all source-buffers content
18960
18973
  // to start playing properly.
18961
- if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
18974
+ const bufferedRanges = bufferInfo.buffered;
18975
+ if ((bufferedRanges && bufferedRanges.length > 1 && bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
18962
18976
  this.warn('Trying to nudge playhead over buffer-hole');
18963
18977
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
18964
18978
  // We only try to jump the hole if it's under the configured size
18965
- // Reset stalled so to rearm watchdog timer
18966
- this.stalled = null;
18967
- this._tryNudgeBuffer();
18979
+ this._tryNudgeBuffer(bufferInfo);
18968
18980
  }
18969
18981
  }
18970
18982
 
@@ -18977,9 +18989,10 @@ class GapController extends Logger {
18977
18989
  const {
18978
18990
  hls,
18979
18991
  media,
18980
- stallReported
18992
+ stallReported,
18993
+ stalled
18981
18994
  } = this;
18982
- if (!stallReported && media) {
18995
+ if (!stallReported && stalled !== null && media && hls) {
18983
18996
  // Report stalled error once
18984
18997
  this.stallReported = true;
18985
18998
  const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
@@ -18989,7 +19002,11 @@ class GapController extends Logger {
18989
19002
  details: ErrorDetails.BUFFER_STALLED_ERROR,
18990
19003
  fatal: false,
18991
19004
  error,
18992
- buffer: bufferInfo.len
19005
+ buffer: bufferInfo.len,
19006
+ bufferInfo,
19007
+ stalled: {
19008
+ start: stalled
19009
+ }
18993
19010
  });
18994
19011
  }
18995
19012
  }
@@ -19000,12 +19017,13 @@ class GapController extends Logger {
19000
19017
  * @private
19001
19018
  */
19002
19019
  _trySkipBufferHole(partial) {
19020
+ var _this$hls3;
19003
19021
  const {
19004
- config,
19005
- hls,
19022
+ fragmentTracker,
19006
19023
  media
19007
19024
  } = this;
19008
- if (media === null) {
19025
+ const config = (_this$hls3 = this.hls) == null ? undefined : _this$hls3.config;
19026
+ if (!media || !fragmentTracker || !config) {
19009
19027
  return 0;
19010
19028
  }
19011
19029
 
@@ -19020,9 +19038,6 @@ class GapController extends Logger {
19020
19038
  if (gapLength > 0 && (bufferStarved || waiting)) {
19021
19039
  // Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial
19022
19040
  if (gapLength > config.maxBufferHole) {
19023
- const {
19024
- fragmentTracker
19025
- } = this;
19026
19041
  let startGap = false;
19027
19042
  if (currentTime === 0) {
19028
19043
  const startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN);
@@ -19053,17 +19068,18 @@ class GapController extends Logger {
19053
19068
  const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
19054
19069
  this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
19055
19070
  this.moved = true;
19056
- this.stalled = null;
19057
19071
  media.currentTime = targetTime;
19058
- if (partial && !partial.gap) {
19072
+ if (partial && !partial.gap && this.hls) {
19059
19073
  const error = new Error(`fragment loaded with buffer holes, seeking from ${currentTime} to ${targetTime}`);
19060
- hls.trigger(Events.ERROR, {
19074
+ this.hls.trigger(Events.ERROR, {
19061
19075
  type: ErrorTypes.MEDIA_ERROR,
19062
19076
  details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
19063
19077
  fatal: false,
19064
19078
  error,
19065
19079
  reason: error.message,
19066
- frag: partial
19080
+ frag: partial,
19081
+ buffer: bufferInfo.len,
19082
+ bufferInfo
19067
19083
  });
19068
19084
  }
19069
19085
  return targetTime;
@@ -19076,15 +19092,15 @@ class GapController extends Logger {
19076
19092
  * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount.
19077
19093
  * @private
19078
19094
  */
19079
- _tryNudgeBuffer() {
19095
+ _tryNudgeBuffer(bufferInfo) {
19080
19096
  const {
19081
- config,
19082
19097
  hls,
19083
19098
  media,
19084
19099
  nudgeRetry
19085
19100
  } = this;
19086
- if (media === null) {
19087
- return;
19101
+ const config = hls == null ? undefined : hls.config;
19102
+ if (!media || !config) {
19103
+ return 0;
19088
19104
  }
19089
19105
  const currentTime = media.currentTime;
19090
19106
  this.nudgeRetry++;
@@ -19098,7 +19114,9 @@ class GapController extends Logger {
19098
19114
  type: ErrorTypes.MEDIA_ERROR,
19099
19115
  details: ErrorDetails.BUFFER_NUDGE_ON_STALL,
19100
19116
  error,
19101
- fatal: false
19117
+ fatal: false,
19118
+ buffer: bufferInfo.len,
19119
+ bufferInfo
19102
19120
  });
19103
19121
  } else {
19104
19122
  const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
@@ -19107,13 +19125,15 @@ class GapController extends Logger {
19107
19125
  type: ErrorTypes.MEDIA_ERROR,
19108
19126
  details: ErrorDetails.BUFFER_STALLED_ERROR,
19109
19127
  error,
19110
- fatal: true
19128
+ fatal: true,
19129
+ buffer: bufferInfo.len,
19130
+ bufferInfo
19111
19131
  });
19112
19132
  }
19113
19133
  }
19114
19134
  }
19115
19135
 
19116
- const version = "1.6.0-beta.2.0.canary.10882";
19136
+ const version = "1.6.0-beta.2.0.canary.10884";
19117
19137
 
19118
19138
  // ensure the worker ends up in the bundle
19119
19139
  // If the worker should not be included this gets aliased to empty.js
@@ -19540,11 +19560,18 @@ class StreamController extends BaseStreamController {
19540
19560
  this.backtrackFragment = null;
19541
19561
  this.audioCodecSwitch = false;
19542
19562
  this.videoBuffer = null;
19563
+ this.onMediaWaiting = () => {
19564
+ const gapController = this.gapController;
19565
+ if (gapController) {
19566
+ gapController.waiting = self.performance.now();
19567
+ }
19568
+ };
19543
19569
  this.onMediaPlaying = () => {
19544
19570
  // tick to speed up FRAG_CHANGED triggering
19545
19571
  const gapController = this.gapController;
19546
19572
  if (gapController) {
19547
19573
  gapController.ended = 0;
19574
+ gapController.waiting = 0;
19548
19575
  }
19549
19576
  this.tick();
19550
19577
  };
@@ -19600,7 +19627,7 @@ class StreamController extends BaseStreamController {
19600
19627
  }
19601
19628
  onHandlerDestroying() {
19602
19629
  // @ts-ignore
19603
- this.onMediaPlaying = this.onMediaSeeked = null;
19630
+ this.onMediaPlaying = this.onMediaSeeked = this.onMediaWaiting = null;
19604
19631
  this.unregisterListeners();
19605
19632
  super.onHandlerDestroying();
19606
19633
  }
@@ -19931,9 +19958,11 @@ class StreamController extends BaseStreamController {
19931
19958
  const media = data.media;
19932
19959
  media.removeEventListener('playing', this.onMediaPlaying);
19933
19960
  media.removeEventListener('seeked', this.onMediaSeeked);
19961
+ media.removeEventListener('waiting', this.onMediaWaiting);
19934
19962
  media.addEventListener('playing', this.onMediaPlaying);
19935
19963
  media.addEventListener('seeked', this.onMediaSeeked);
19936
- this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
19964
+ media.addEventListener('waiting', this.onMediaWaiting);
19965
+ this.gapController = new GapController(media, this.fragmentTracker, this.hls);
19937
19966
  }
19938
19967
  onMediaDetaching(event, data) {
19939
19968
  const {
@@ -19942,6 +19971,7 @@ class StreamController extends BaseStreamController {
19942
19971
  if (media) {
19943
19972
  media.removeEventListener('playing', this.onMediaPlaying);
19944
19973
  media.removeEventListener('seeked', this.onMediaSeeked);
19974
+ media.removeEventListener('waiting', this.onMediaWaiting);
19945
19975
  }
19946
19976
  this.videoBuffer = null;
19947
19977
  this.fragPlaying = null;
@@ -20369,7 +20399,7 @@ class StreamController extends BaseStreamController {
20369
20399
  let startPosition = this.startPosition;
20370
20400
  // only adjust currentTime if different from startPosition or if startPosition not buffered
20371
20401
  // at that stage, there should be only one buffered range, as we reach that code after first fragment has been buffered
20372
- if (startPosition >= 0) {
20402
+ if (startPosition >= 0 && currentTime < startPosition) {
20373
20403
  if (media.seeking) {
20374
20404
  this.log(`could not seek to ${startPosition}, already seeking at ${currentTime}`);
20375
20405
  return;