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.js CHANGED
@@ -785,6 +785,7 @@
785
785
  Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
786
786
  Events["MEDIA_DETACHED"] = "hlsMediaDetached";
787
787
  Events["MEDIA_ENDED"] = "hlsMediaEnded";
788
+ Events["STALL_RESOLVED"] = "hlsStallResolved";
788
789
  Events["BUFFER_RESET"] = "hlsBufferReset";
789
790
  Events["BUFFER_CODECS"] = "hlsBufferCodecs";
790
791
  Events["BUFFER_CREATED"] = "hlsBufferCreated";
@@ -1058,7 +1059,7 @@
1058
1059
  // Some browsers don't allow to use bind on console object anyway
1059
1060
  // fallback to default if needed
1060
1061
  try {
1061
- newLogger.log("Debug logs enabled for \"" + context + "\" in hls.js version " + "1.6.0-beta.2.0.canary.10882");
1062
+ newLogger.log("Debug logs enabled for \"" + context + "\" in hls.js version " + "1.6.0-beta.2.0.canary.10884");
1062
1063
  } catch (e) {
1063
1064
  /* log fn threw an exception. All logger methods are no-ops. */
1064
1065
  return createLogger();
@@ -6521,8 +6522,7 @@
6521
6522
  return {
6522
6523
  len: 0,
6523
6524
  start: pos,
6524
- end: pos,
6525
- nextStart: undefined
6525
+ end: pos
6526
6526
  };
6527
6527
  };
6528
6528
  BufferHelper.bufferedInfo = function bufferedInfo(buffered, pos, maxHoleDuration) {
@@ -6587,7 +6587,8 @@
6587
6587
  len: bufferLen,
6588
6588
  start: bufferStart || 0,
6589
6589
  end: bufferEnd || 0,
6590
- nextStart: bufferStartNext
6590
+ nextStart: bufferStartNext,
6591
+ buffered: buffered
6591
6592
  };
6592
6593
  }
6593
6594
 
@@ -6986,8 +6987,6 @@
6986
6987
  this.advancedDateTime = undefined;
6987
6988
  this.updated = true;
6988
6989
  this.advanced = true;
6989
- this.availabilityDelay = undefined;
6990
- // Manifest reload synchronization
6991
6990
  this.misses = 0;
6992
6991
  this.startCC = 0;
6993
6992
  this.startSN = 0;
@@ -7040,7 +7039,6 @@
7040
7039
  } else {
7041
7040
  this.misses = previous.misses + 1;
7042
7041
  }
7043
- this.availabilityDelay = previous.availabilityDelay;
7044
7042
  };
7045
7043
  return _createClass(LevelDetails, [{
7046
7044
  key: "hasProgramDateTime",
@@ -8042,7 +8040,7 @@
8042
8040
  if (!level.live) {
8043
8041
  lastFragment.endList = true;
8044
8042
  }
8045
- if (firstFragment && !level.startCC) {
8043
+ if (firstFragment && level.startCC === undefined) {
8046
8044
  level.startCC = firstFragment.cc;
8047
8045
  }
8048
8046
  /**
@@ -8332,15 +8330,16 @@
8332
8330
  delete oldDetails.fragmentHint.endPTS;
8333
8331
  }
8334
8332
  // check if old/new playlists have fragments in common
8335
- // loop through overlapping SN and update startPTS , cc, and duration if any found
8336
- var ccOffset = 0;
8333
+ // loop through overlapping SN and update startPTS, cc, and duration if any found
8337
8334
  var PTSFrag;
8338
- mapFragmentIntersection(oldDetails, newDetails, function (oldFrag, newFrag) {
8339
- if (oldFrag.relurl) {
8340
- // Do not compare CC if the old fragment has no url. This is a level.fragmentHint used by LL-HLS parts.
8341
- // It maybe be off by 1 if it was created before any parts or discontinuity tags were appended to the end
8342
- // of the playlist.
8343
- ccOffset = oldFrag.cc - newFrag.cc;
8335
+ mapFragmentIntersection(oldDetails, newDetails, function (oldFrag, newFrag, newFragIndex, newFragments) {
8336
+ if (newDetails.skippedSegments) {
8337
+ if (newFrag.cc !== oldFrag.cc) {
8338
+ var ccOffset = oldFrag.cc - newFrag.cc;
8339
+ for (var _i = newFragIndex; _i < newFragments.length; _i++) {
8340
+ newFragments[_i].cc += ccOffset;
8341
+ }
8342
+ }
8344
8343
  }
8345
8344
  if (isFiniteNumber(oldFrag.startPTS) && isFiniteNumber(oldFrag.endPTS)) {
8346
8345
  newFrag.setStart(newFrag.startPTS = oldFrag.startPTS);
@@ -8369,7 +8368,8 @@
8369
8368
  currentInitSegment = oldFrag.initSegment;
8370
8369
  }
8371
8370
  });
8372
- var fragmentsToCheck = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments;
8371
+ var newFragments = newDetails.fragments;
8372
+ var fragmentsToCheck = newDetails.fragmentHint ? newFragments.concat(newDetails.fragmentHint) : newFragments;
8373
8373
  if (currentInitSegment) {
8374
8374
  fragmentsToCheck.forEach(function (frag) {
8375
8375
  var _currentInitSegment;
@@ -8379,19 +8379,17 @@
8379
8379
  });
8380
8380
  }
8381
8381
  if (newDetails.skippedSegments) {
8382
- newDetails.deltaUpdateFailed = newDetails.fragments.some(function (frag) {
8382
+ newDetails.deltaUpdateFailed = newFragments.some(function (frag) {
8383
8383
  return !frag;
8384
8384
  });
8385
8385
  if (newDetails.deltaUpdateFailed) {
8386
8386
  logger.warn('[level-helper] Previous playlist missing segments skipped in delta playlist');
8387
- for (var _i = newDetails.skippedSegments; _i--;) {
8388
- newDetails.fragments.shift();
8389
- }
8390
- newDetails.startSN = newDetails.fragments[0].sn;
8391
- if (!newDetails.startCC) {
8392
- newDetails.startCC = newDetails.fragments[0].cc;
8387
+ for (var _i2 = newDetails.skippedSegments; _i2--;) {
8388
+ newFragments.shift();
8393
8389
  }
8390
+ newDetails.startSN = newFragments[0].sn;
8394
8391
  } else {
8392
+ newDetails.endCC = newFragments[newFragments.length - 1].cc;
8395
8393
  if (newDetails.canSkipDateRanges) {
8396
8394
  newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails);
8397
8395
  }
@@ -8399,25 +8397,15 @@
8399
8397
  return frag.rawProgramDateTime;
8400
8398
  });
8401
8399
  if (oldDetails.hasProgramDateTime && !newDetails.hasProgramDateTime) {
8402
- for (var _i2 = 1; _i2 < fragmentsToCheck.length; _i2++) {
8403
- if (fragmentsToCheck[_i2].programDateTime === null) {
8404
- assignProgramDateTime(fragmentsToCheck[_i2], fragmentsToCheck[_i2 - 1], programDateTimes);
8400
+ for (var _i3 = 1; _i3 < fragmentsToCheck.length; _i3++) {
8401
+ if (fragmentsToCheck[_i3].programDateTime === null) {
8402
+ assignProgramDateTime(fragmentsToCheck[_i3], fragmentsToCheck[_i3 - 1], programDateTimes);
8405
8403
  }
8406
8404
  }
8407
8405
  }
8408
8406
  mapDateRanges(programDateTimes, newDetails);
8409
8407
  }
8410
8408
  }
8411
- var newFragments = newDetails.fragments;
8412
- if (ccOffset) {
8413
- logger.warn('discontinuity sliding from playlist, take drift into account');
8414
- for (var _i3 = 0; _i3 < newFragments.length; _i3++) {
8415
- newFragments[_i3].cc += ccOffset;
8416
- }
8417
- }
8418
- if (newDetails.skippedSegments) {
8419
- newDetails.startCC = newDetails.fragments[0].cc;
8420
- }
8421
8409
 
8422
8410
  // Merge parts
8423
8411
  mapPartIntersection(oldDetails.partList, newDetails.partList, function (oldPart, newPart) {
@@ -8513,7 +8501,7 @@
8513
8501
  _newFrag = newDetails.fragments[i] = _oldFrag;
8514
8502
  }
8515
8503
  if (_oldFrag && _newFrag) {
8516
- intersectionFn(_oldFrag, _newFrag);
8504
+ intersectionFn(_oldFrag, _newFrag, i, newFrags);
8517
8505
  }
8518
8506
  }
8519
8507
  }
@@ -16320,7 +16308,7 @@
16320
16308
  return !remuxResult.audio && !remuxResult.video && !remuxResult.text && !remuxResult.id3 && !remuxResult.initSegment;
16321
16309
  }
16322
16310
 
16323
- var version = "1.6.0-beta.2.0.canary.10882";
16311
+ var version = "1.6.0-beta.2.0.canary.10884";
16324
16312
 
16325
16313
  // ensure the worker ends up in the bundle
16326
16314
  // If the worker should not be included this gets aliased to empty.js
@@ -30362,6 +30350,7 @@
30362
30350
  progressive: false,
30363
30351
  lowLatencyMode: true,
30364
30352
  cmcd: undefined,
30353
+ detectStallWithCurrentTimeMs: 1250,
30365
30354
  enableDateRangeMetadataCues: true,
30366
30355
  enableEmsgMetadataCues: true,
30367
30356
  enableEmsgKLVMetadata: false,
@@ -31787,25 +31776,23 @@
31787
31776
  });
31788
31777
  }
31789
31778
 
31790
- var STALL_MINIMUM_DURATION_MS = 250;
31791
31779
  var MAX_START_GAP_JUMP = 2.0;
31792
31780
  var SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
31793
31781
  var SKIP_BUFFER_RANGE_START = 0.05;
31794
31782
  var GapController = /*#__PURE__*/function (_Logger) {
31795
- function GapController(config, media, fragmentTracker, hls) {
31783
+ function GapController(media, fragmentTracker, hls) {
31796
31784
  var _this;
31797
31785
  _this = _Logger.call(this, 'gap-controller', hls.logger) || this;
31798
- _this.config = undefined;
31799
31786
  _this.media = null;
31800
- _this.fragmentTracker = undefined;
31801
- _this.hls = undefined;
31787
+ _this.fragmentTracker = null;
31788
+ _this.hls = null;
31802
31789
  _this.nudgeRetry = 0;
31803
31790
  _this.stallReported = false;
31804
31791
  _this.stalled = null;
31805
31792
  _this.moved = false;
31806
31793
  _this.seeking = false;
31807
31794
  _this.ended = 0;
31808
- _this.config = config;
31795
+ _this.waiting = 0;
31809
31796
  _this.media = media;
31810
31797
  _this.fragmentTracker = fragmentTracker;
31811
31798
  _this.hls = hls;
@@ -31814,9 +31801,7 @@
31814
31801
  _inheritsLoose(GapController, _Logger);
31815
31802
  var _proto = GapController.prototype;
31816
31803
  _proto.destroy = function destroy() {
31817
- this.media = null;
31818
- // @ts-ignore
31819
- this.hls = this.fragmentTracker = null;
31804
+ this.media = this.hls = this.fragmentTracker = null;
31820
31805
  }
31821
31806
 
31822
31807
  /**
@@ -31826,10 +31811,10 @@
31826
31811
  * @param lastCurrentTime - Previously read playhead position
31827
31812
  */;
31828
31813
  _proto.poll = function poll(lastCurrentTime, activeFrag, levelDetails, state) {
31829
- var config = this.config,
31830
- media = this.media,
31814
+ var _this$hls;
31815
+ var media = this.media,
31831
31816
  stalled = this.stalled;
31832
- if (media === null) {
31817
+ if (!media) {
31833
31818
  return;
31834
31819
  }
31835
31820
  var currentTime = media.currentTime,
@@ -31847,43 +31832,45 @@
31847
31832
  if (!seeking) {
31848
31833
  this.nudgeRetry = 0;
31849
31834
  }
31850
- if (stalled !== null) {
31851
- // The playhead is now moving, but was previously stalled
31852
- if (this.stallReported) {
31853
- var _stalledDuration = self.performance.now() - stalled;
31854
- this.warn("playback not stuck anymore @" + currentTime + ", after " + Math.round(_stalledDuration) + "ms");
31855
- this.stallReported = false;
31856
- }
31857
- this.stalled = null;
31835
+ if (this.waiting === 0) {
31836
+ this.stallResolved(currentTime);
31858
31837
  }
31859
31838
  return;
31860
31839
  }
31861
31840
 
31862
31841
  // Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek
31863
31842
  if (beginSeek || seeked) {
31864
- this.stalled = null;
31843
+ if (seeked) {
31844
+ this.stallResolved(currentTime);
31845
+ }
31865
31846
  return;
31866
31847
  }
31867
31848
 
31868
31849
  // The playhead should not be moving
31869
- if (media.paused && !seeking || media.ended || media.playbackRate === 0 || !BufferHelper.getBuffered(media).length) {
31850
+ if (media.paused && !seeking || media.ended || media.playbackRate === 0) {
31851
+ this.nudgeRetry = 0;
31852
+ this.stallResolved(currentTime);
31870
31853
  // Fire MEDIA_ENDED to workaround event not being dispatched by browser
31871
- if (!this.ended && media.ended) {
31854
+ if (!this.ended && media.ended && this.hls) {
31872
31855
  this.ended = currentTime || 1;
31873
31856
  this.hls.trigger(Events.MEDIA_ENDED, {
31874
31857
  stalled: false
31875
31858
  });
31876
31859
  }
31860
+ return;
31861
+ }
31862
+ if (!BufferHelper.getBuffered(media).length) {
31877
31863
  this.nudgeRetry = 0;
31878
31864
  return;
31879
31865
  }
31880
31866
  var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
31881
31867
  var nextStart = bufferInfo.nextStart || 0;
31882
- if (seeking) {
31868
+ var fragmentTracker = this.fragmentTracker;
31869
+ if (seeking && fragmentTracker) {
31883
31870
  // Waiting for seeking in a buffered range to complete
31884
31871
  var hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
31885
31872
  // Next buffered range is too far ahead to jump to while still seeking
31886
- var noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !this.fragmentTracker.getPartialFragment(currentTime);
31873
+ var noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !fragmentTracker.getPartialFragment(currentTime);
31887
31874
  if (hasEnoughBuffer || noBufferGap) {
31888
31875
  return;
31889
31876
  }
@@ -31893,7 +31880,7 @@
31893
31880
 
31894
31881
  // Skip start gaps if we haven't played, but the last poll detected the start of a stall
31895
31882
  // The addition poll gives the browser a chance to jump the gap for us
31896
- if (!this.moved && this.stalled !== null) {
31883
+ if (!this.moved && this.stalled !== null && fragmentTracker) {
31897
31884
  // There is no playable buffer (seeked, waiting for buffer)
31898
31885
  var isBuffered = bufferInfo.len > 0;
31899
31886
  if (!isBuffered && !nextStart) {
@@ -31907,7 +31894,7 @@
31907
31894
  // that begins over 1 target duration after the video start position.
31908
31895
  var isLive = !!(levelDetails != null && levelDetails.live);
31909
31896
  var maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
31910
- var partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
31897
+ var partialOrGap = fragmentTracker.getPartialFragment(currentTime);
31911
31898
  if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
31912
31899
  if (!media.paused) {
31913
31900
  this._trySkipBufferHole(partialOrGap);
@@ -31917,16 +31904,27 @@
31917
31904
  }
31918
31905
 
31919
31906
  // Start tracking stall time
31907
+ var config = (_this$hls = this.hls) == null ? undefined : _this$hls.config;
31908
+ if (!config) {
31909
+ return;
31910
+ }
31911
+ var detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs;
31920
31912
  var tnow = self.performance.now();
31913
+ var tWaiting = this.waiting;
31921
31914
  if (stalled === null) {
31922
- this.stalled = tnow;
31915
+ // Use time of recent "waiting" event
31916
+ if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) {
31917
+ this.stalled = tWaiting;
31918
+ } else {
31919
+ this.stalled = tnow;
31920
+ }
31923
31921
  return;
31924
31922
  }
31925
31923
  var stalledDuration = tnow - stalled;
31926
- if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
31924
+ if (!seeking && (stalledDuration >= detectStallWithCurrentTimeMs || tWaiting) && this.hls) {
31927
31925
  // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
31928
31926
  if (state === State.ENDED && !(levelDetails != null && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? undefined : levelDetails.edge) || 0)) < 1) {
31929
- if (stalledDuration < 1000 || this.ended) {
31927
+ if (this.ended) {
31930
31928
  return;
31931
31929
  }
31932
31930
  this.ended = currentTime || 1;
@@ -31937,12 +31935,26 @@
31937
31935
  }
31938
31936
  // Report stalling after trying to fix
31939
31937
  this._reportStall(bufferInfo);
31940
- if (!this.media) {
31938
+ if (!this.media || !this.hls) {
31941
31939
  return;
31942
31940
  }
31943
31941
  }
31944
31942
  var bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole);
31945
31943
  this._tryFixBufferStall(bufferedWithHoles, stalledDuration);
31944
+ };
31945
+ _proto.stallResolved = function stallResolved(currentTime) {
31946
+ var stalled = this.stalled;
31947
+ if (stalled && this.hls) {
31948
+ this.stalled = null;
31949
+ // The playhead is now moving, but was previously stalled
31950
+ if (this.stallReported) {
31951
+ var stalledDuration = self.performance.now() - stalled;
31952
+ this.warn("playback not stuck anymore @" + currentTime + ", after " + Math.round(stalledDuration) + "ms");
31953
+ this.stallReported = false;
31954
+ this.waiting = 0;
31955
+ this.hls.trigger(Events.STALL_RESOLVED, {});
31956
+ }
31957
+ }
31946
31958
  }
31947
31959
 
31948
31960
  /**
@@ -31952,10 +31964,11 @@
31952
31964
  * @private
31953
31965
  */;
31954
31966
  _proto._tryFixBufferStall = function _tryFixBufferStall(bufferInfo, stalledDurationMs) {
31955
- var config = this.config,
31956
- fragmentTracker = this.fragmentTracker,
31967
+ var _this$hls2;
31968
+ var fragmentTracker = this.fragmentTracker,
31957
31969
  media = this.media;
31958
- if (media === null) {
31970
+ var config = (_this$hls2 = this.hls) == null ? undefined : _this$hls2.config;
31971
+ if (!media || !fragmentTracker || !config) {
31959
31972
  return;
31960
31973
  }
31961
31974
  var currentTime = media.currentTime;
@@ -31975,13 +31988,12 @@
31975
31988
  // we may just have to "nudge" the playlist as the browser decoding/rendering engine
31976
31989
  // needs to cross some sort of threshold covering all source-buffers content
31977
31990
  // to start playing properly.
31978
- if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
31991
+ var bufferedRanges = bufferInfo.buffered;
31992
+ if ((bufferedRanges && bufferedRanges.length > 1 && bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
31979
31993
  this.warn('Trying to nudge playhead over buffer-hole');
31980
31994
  // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
31981
31995
  // We only try to jump the hole if it's under the configured size
31982
- // Reset stalled so to rearm watchdog timer
31983
- this.stalled = null;
31984
- this._tryNudgeBuffer();
31996
+ this._tryNudgeBuffer(bufferInfo);
31985
31997
  }
31986
31998
  }
31987
31999
 
@@ -31993,8 +32005,9 @@
31993
32005
  _proto._reportStall = function _reportStall(bufferInfo) {
31994
32006
  var hls = this.hls,
31995
32007
  media = this.media,
31996
- stallReported = this.stallReported;
31997
- if (!stallReported && media) {
32008
+ stallReported = this.stallReported,
32009
+ stalled = this.stalled;
32010
+ if (!stallReported && stalled !== null && media && hls) {
31998
32011
  // Report stalled error once
31999
32012
  this.stallReported = true;
32000
32013
  var error = new Error("Playback stalling at @" + media.currentTime + " due to low buffer (" + JSON.stringify(bufferInfo) + ")");
@@ -32004,7 +32017,11 @@
32004
32017
  details: ErrorDetails.BUFFER_STALLED_ERROR,
32005
32018
  fatal: false,
32006
32019
  error: error,
32007
- buffer: bufferInfo.len
32020
+ buffer: bufferInfo.len,
32021
+ bufferInfo: bufferInfo,
32022
+ stalled: {
32023
+ start: stalled
32024
+ }
32008
32025
  });
32009
32026
  }
32010
32027
  }
@@ -32015,10 +32032,11 @@
32015
32032
  * @private
32016
32033
  */;
32017
32034
  _proto._trySkipBufferHole = function _trySkipBufferHole(partial) {
32018
- var config = this.config,
32019
- hls = this.hls,
32035
+ var _this$hls3;
32036
+ var fragmentTracker = this.fragmentTracker,
32020
32037
  media = this.media;
32021
- if (media === null) {
32038
+ var config = (_this$hls3 = this.hls) == null ? undefined : _this$hls3.config;
32039
+ if (!media || !fragmentTracker || !config) {
32022
32040
  return 0;
32023
32041
  }
32024
32042
 
@@ -32033,7 +32051,6 @@
32033
32051
  if (gapLength > 0 && (bufferStarved || waiting)) {
32034
32052
  // Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial
32035
32053
  if (gapLength > config.maxBufferHole) {
32036
- var fragmentTracker = this.fragmentTracker;
32037
32054
  var startGap = false;
32038
32055
  if (currentTime === 0) {
32039
32056
  var startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN);
@@ -32064,17 +32081,18 @@
32064
32081
  var targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
32065
32082
  this.warn("skipping hole, adjusting currentTime from " + currentTime + " to " + targetTime);
32066
32083
  this.moved = true;
32067
- this.stalled = null;
32068
32084
  media.currentTime = targetTime;
32069
- if (partial && !partial.gap) {
32085
+ if (partial && !partial.gap && this.hls) {
32070
32086
  var error = new Error("fragment loaded with buffer holes, seeking from " + currentTime + " to " + targetTime);
32071
- hls.trigger(Events.ERROR, {
32087
+ this.hls.trigger(Events.ERROR, {
32072
32088
  type: ErrorTypes.MEDIA_ERROR,
32073
32089
  details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
32074
32090
  fatal: false,
32075
32091
  error: error,
32076
32092
  reason: error.message,
32077
- frag: partial
32093
+ frag: partial,
32094
+ buffer: bufferInfo.len,
32095
+ bufferInfo: bufferInfo
32078
32096
  });
32079
32097
  }
32080
32098
  return targetTime;
@@ -32087,13 +32105,13 @@
32087
32105
  * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount.
32088
32106
  * @private
32089
32107
  */;
32090
- _proto._tryNudgeBuffer = function _tryNudgeBuffer() {
32091
- var config = this.config,
32092
- hls = this.hls,
32108
+ _proto._tryNudgeBuffer = function _tryNudgeBuffer(bufferInfo) {
32109
+ var hls = this.hls,
32093
32110
  media = this.media,
32094
32111
  nudgeRetry = this.nudgeRetry;
32095
- if (media === null) {
32096
- return;
32112
+ var config = hls == null ? undefined : hls.config;
32113
+ if (!media || !config) {
32114
+ return 0;
32097
32115
  }
32098
32116
  var currentTime = media.currentTime;
32099
32117
  this.nudgeRetry++;
@@ -32107,7 +32125,9 @@
32107
32125
  type: ErrorTypes.MEDIA_ERROR,
32108
32126
  details: ErrorDetails.BUFFER_NUDGE_ON_STALL,
32109
32127
  error: error,
32110
- fatal: false
32128
+ fatal: false,
32129
+ buffer: bufferInfo.len,
32130
+ bufferInfo: bufferInfo
32111
32131
  });
32112
32132
  } else {
32113
32133
  var _error = new Error("Playhead still not moving while enough data buffered @" + currentTime + " after " + config.nudgeMaxRetry + " nudges");
@@ -32116,7 +32136,9 @@
32116
32136
  type: ErrorTypes.MEDIA_ERROR,
32117
32137
  details: ErrorDetails.BUFFER_STALLED_ERROR,
32118
32138
  error: _error,
32119
- fatal: true
32139
+ fatal: true,
32140
+ buffer: bufferInfo.len,
32141
+ bufferInfo: bufferInfo
32120
32142
  });
32121
32143
  }
32122
32144
  };
@@ -32172,11 +32194,18 @@
32172
32194
  _this.backtrackFragment = null;
32173
32195
  _this.audioCodecSwitch = false;
32174
32196
  _this.videoBuffer = null;
32197
+ _this.onMediaWaiting = function () {
32198
+ var gapController = _this.gapController;
32199
+ if (gapController) {
32200
+ gapController.waiting = self.performance.now();
32201
+ }
32202
+ };
32175
32203
  _this.onMediaPlaying = function () {
32176
32204
  // tick to speed up FRAG_CHANGED triggering
32177
32205
  var gapController = _this.gapController;
32178
32206
  if (gapController) {
32179
32207
  gapController.ended = 0;
32208
+ gapController.waiting = 0;
32180
32209
  }
32181
32210
  _this.tick();
32182
32211
  };
@@ -32231,7 +32260,7 @@
32231
32260
  };
32232
32261
  _proto.onHandlerDestroying = function onHandlerDestroying() {
32233
32262
  // @ts-ignore
32234
- this.onMediaPlaying = this.onMediaSeeked = null;
32263
+ this.onMediaPlaying = this.onMediaSeeked = this.onMediaWaiting = null;
32235
32264
  this.unregisterListeners();
32236
32265
  _BaseStreamController.prototype.onHandlerDestroying.call(this);
32237
32266
  };
@@ -32552,15 +32581,18 @@
32552
32581
  var media = data.media;
32553
32582
  media.removeEventListener('playing', this.onMediaPlaying);
32554
32583
  media.removeEventListener('seeked', this.onMediaSeeked);
32584
+ media.removeEventListener('waiting', this.onMediaWaiting);
32555
32585
  media.addEventListener('playing', this.onMediaPlaying);
32556
32586
  media.addEventListener('seeked', this.onMediaSeeked);
32557
- this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
32587
+ media.addEventListener('waiting', this.onMediaWaiting);
32588
+ this.gapController = new GapController(media, this.fragmentTracker, this.hls);
32558
32589
  };
32559
32590
  _proto.onMediaDetaching = function onMediaDetaching(event, data) {
32560
32591
  var media = this.media;
32561
32592
  if (media) {
32562
32593
  media.removeEventListener('playing', this.onMediaPlaying);
32563
32594
  media.removeEventListener('seeked', this.onMediaSeeked);
32595
+ media.removeEventListener('waiting', this.onMediaWaiting);
32564
32596
  }
32565
32597
  this.videoBuffer = null;
32566
32598
  this.fragPlaying = null;
@@ -32970,7 +33002,7 @@
32970
33002
  var startPosition = this.startPosition;
32971
33003
  // only adjust currentTime if different from startPosition or if startPosition not buffered
32972
33004
  // at that stage, there should be only one buffered range, as we reach that code after first fragment has been buffered
32973
- if (startPosition >= 0) {
33005
+ if (startPosition >= 0 && currentTime < startPosition) {
32974
33006
  if (media.seeking) {
32975
33007
  this.log("could not seek to " + startPosition + ", already seeking at " + currentTime);
32976
33008
  return;
package/dist/hls.js.d.ts CHANGED
@@ -571,6 +571,19 @@ export declare type BufferInfo = {
571
571
  start: number;
572
572
  end: number;
573
573
  nextStart?: number;
574
+ buffered?: BufferTimeRange[];
575
+ };
576
+
577
+ /**
578
+ * Provides methods dealing with buffer length retrieval for example.
579
+ *
580
+ * In general, a helper around HTML5 MediaElement TimeRanges gathered from `buffered` property.
581
+ *
582
+ * Also @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/buffered
583
+ */
584
+ export declare type BufferTimeRange = {
585
+ start: number;
586
+ end: number;
574
587
  };
575
588
 
576
589
  export declare class CapLevelController implements ComponentAPI {
@@ -1017,6 +1030,7 @@ export declare interface ErrorData {
1017
1030
  fatal: boolean;
1018
1031
  errorAction?: IErrorAction;
1019
1032
  buffer?: number;
1033
+ bufferInfo?: BufferInfo;
1020
1034
  bytes?: number;
1021
1035
  chunkMeta?: ChunkMetadata;
1022
1036
  context?: PlaylistLoaderContext;
@@ -1027,6 +1041,9 @@ export declare interface ErrorData {
1027
1041
  levelRetry?: boolean;
1028
1042
  loader?: Loader<LoaderContext>;
1029
1043
  networkDetails?: any;
1044
+ stalled?: {
1045
+ start: number;
1046
+ };
1030
1047
  stats?: LoaderStats;
1031
1048
  mimeType?: string;
1032
1049
  reason?: string;
@@ -1110,6 +1127,7 @@ export declare enum Events {
1110
1127
  MEDIA_DETACHING = "hlsMediaDetaching",
1111
1128
  MEDIA_DETACHED = "hlsMediaDetached",
1112
1129
  MEDIA_ENDED = "hlsMediaEnded",
1130
+ STALL_RESOLVED = "hlsStallResolved",
1113
1131
  BUFFER_RESET = "hlsBufferReset",
1114
1132
  BUFFER_CODECS = "hlsBufferCodecs",
1115
1133
  BUFFER_CREATED = "hlsBufferCreated",
@@ -1948,6 +1966,7 @@ export declare type HlsConfig = {
1948
1966
  progressive: boolean;
1949
1967
  lowLatencyMode: boolean;
1950
1968
  primarySessionId?: string;
1969
+ detectStallWithCurrentTimeMs: number;
1951
1970
  } & ABRControllerConfig & BufferControllerConfig & CapLevelControllerConfig & EMEControllerConfig & FPSControllerConfig & LevelControllerConfig & MP4RemuxerConfig & StreamControllerConfig & SelectionPreferences & LatencyControllerConfig & MetadataControllerConfig & TimelineControllerConfig & TSDemuxerConfig & HlsLoadPolicies & FragmentLoaderConfig & PlaylistLoaderConfig;
1952
1971
 
1953
1972
  export declare interface HlsEventEmitter {
@@ -1969,6 +1988,7 @@ export declare interface HlsListeners {
1969
1988
  [Events.MEDIA_DETACHING]: (event: Events.MEDIA_DETACHING, data: MediaDetachingData) => void;
1970
1989
  [Events.MEDIA_DETACHED]: (event: Events.MEDIA_DETACHED, data: MediaDetachedData) => void;
1971
1990
  [Events.MEDIA_ENDED]: (event: Events.MEDIA_ENDED, data: MediaEndedData) => void;
1991
+ [Events.STALL_RESOLVED]: (event: Events.STALL_RESOLVED, data: {}) => void;
1972
1992
  [Events.BUFFER_RESET]: (event: Events.BUFFER_RESET) => void;
1973
1993
  [Events.BUFFER_CODECS]: (event: Events.BUFFER_CODECS, data: BufferCodecsData) => void;
1974
1994
  [Events.BUFFER_CREATED]: (event: Events.BUFFER_CREATED, data: BufferCreatedData) => void;
@@ -2558,7 +2578,6 @@ export declare class LevelDetails {
2558
2578
  advancedDateTime?: number;
2559
2579
  updated: boolean;
2560
2580
  advanced: boolean;
2561
- availabilityDelay?: number;
2562
2581
  misses: number;
2563
2582
  startCC: number;
2564
2583
  startSN: number;
@@ -3340,6 +3359,7 @@ export declare class StreamController extends BaseStreamController implements Ne
3340
3359
  protected flushMainBuffer(startOffset: number, endOffset: number): void;
3341
3360
  protected onMediaAttached(event: Events.MEDIA_ATTACHED, data: MediaAttachedData): void;
3342
3361
  protected onMediaDetaching(event: Events.MEDIA_DETACHING, data: MediaDetachingData): void;
3362
+ private onMediaWaiting;
3343
3363
  private onMediaPlaying;
3344
3364
  private onMediaSeeked;
3345
3365
  protected triggerEnded(): void;