hls.js 1.6.0-beta.2.0.canary.10924 → 1.6.0-beta.2.0.canary.10925

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
@@ -1059,7 +1059,7 @@
1059
1059
  // Some browsers don't allow to use bind on console object anyway
1060
1060
  // fallback to default if needed
1061
1061
  try {
1062
- newLogger.log("Debug logs enabled for \"" + context + "\" in hls.js version " + "1.6.0-beta.2.0.canary.10924");
1062
+ newLogger.log("Debug logs enabled for \"" + context + "\" in hls.js version " + "1.6.0-beta.2.0.canary.10925");
1063
1063
  } catch (e) {
1064
1064
  /* log fn threw an exception. All logger methods are no-ops. */
1065
1065
  return createLogger();
@@ -6504,38 +6504,55 @@
6504
6504
  }
6505
6505
  return false;
6506
6506
  };
6507
+ BufferHelper.bufferedRanges = function bufferedRanges(media) {
6508
+ if (media) {
6509
+ var timeRanges = BufferHelper.getBuffered(media);
6510
+ return BufferHelper.timeRangesToArray(timeRanges);
6511
+ }
6512
+ return [];
6513
+ };
6514
+ BufferHelper.timeRangesToArray = function timeRangesToArray(timeRanges) {
6515
+ var buffered = [];
6516
+ for (var i = 0; i < timeRanges.length; i++) {
6517
+ buffered.push({
6518
+ start: timeRanges.start(i),
6519
+ end: timeRanges.end(i)
6520
+ });
6521
+ }
6522
+ return buffered;
6523
+ };
6507
6524
  BufferHelper.bufferInfo = function bufferInfo(media, pos, maxHoleDuration) {
6508
6525
  if (media) {
6509
- var vbuffered = BufferHelper.getBuffered(media);
6510
- if (vbuffered.length) {
6511
- var buffered = [];
6512
- for (var i = 0; i < vbuffered.length; i++) {
6513
- buffered.push({
6514
- start: vbuffered.start(i),
6515
- end: vbuffered.end(i)
6516
- });
6517
- }
6526
+ var buffered = BufferHelper.bufferedRanges(media);
6527
+ if (buffered.length) {
6518
6528
  return BufferHelper.bufferedInfo(buffered, pos, maxHoleDuration);
6519
6529
  }
6520
6530
  }
6521
6531
  return {
6522
6532
  len: 0,
6523
6533
  start: pos,
6524
- end: pos
6534
+ end: pos,
6535
+ bufferedIndex: -1
6525
6536
  };
6526
6537
  };
6527
6538
  BufferHelper.bufferedInfo = function bufferedInfo(buffered, pos, maxHoleDuration) {
6528
6539
  pos = Math.max(0, pos);
6529
6540
  // sort on buffer.start/smaller end (IE does not always return sorted buffered range)
6530
- buffered.sort(function (a, b) {
6531
- return a.start - b.start || b.end - a.end;
6532
- });
6541
+ if (buffered.length > 1) {
6542
+ buffered.sort(function (a, b) {
6543
+ return a.start - b.start || b.end - a.end;
6544
+ });
6545
+ }
6546
+ var bufferedIndex = -1;
6533
6547
  var buffered2 = [];
6534
6548
  if (maxHoleDuration) {
6535
6549
  // there might be some small holes between buffer time range
6536
6550
  // consider that holes smaller than maxHoleDuration are irrelevant and build another
6537
6551
  // buffer time range representations that discards those holes
6538
6552
  for (var i = 0; i < buffered.length; i++) {
6553
+ if (pos >= buffered[i].start && pos <= buffered[i].end) {
6554
+ bufferedIndex = i;
6555
+ }
6539
6556
  var buf2len = buffered2.length;
6540
6557
  if (buf2len) {
6541
6558
  var buf2end = buffered2[buf2len - 1].end;
@@ -6561,24 +6578,25 @@
6561
6578
  buffered2 = buffered;
6562
6579
  }
6563
6580
  var bufferLen = 0;
6581
+ var nextStart;
6564
6582
 
6565
- // bufferStartNext can possibly be undefined based on the conditional logic below
6566
- var bufferStartNext;
6567
-
6568
- // bufferStart and bufferEnd are buffer boundaries around current video position
6583
+ // bufferStart and bufferEnd are buffer boundaries around current playback position (pos)
6569
6584
  var bufferStart = pos;
6570
6585
  var bufferEnd = pos;
6571
6586
  for (var _i = 0; _i < buffered2.length; _i++) {
6572
6587
  var start = buffered2[_i].start;
6573
6588
  var end = buffered2[_i].end;
6574
6589
  // logger.log('buf start/end:' + buffered.start(i) + '/' + buffered.end(i));
6590
+ if (bufferedIndex === -1 && pos >= start && pos <= end) {
6591
+ bufferedIndex = _i;
6592
+ }
6575
6593
  if (pos + maxHoleDuration >= start && pos < end) {
6576
6594
  // play position is inside this buffer TimeRange, retrieve end of buffer position and buffer length
6577
6595
  bufferStart = start;
6578
6596
  bufferEnd = end;
6579
6597
  bufferLen = bufferEnd - pos;
6580
6598
  } else if (pos + maxHoleDuration < start) {
6581
- bufferStartNext = start;
6599
+ nextStart = start;
6582
6600
  break;
6583
6601
  }
6584
6602
  }
@@ -6586,8 +6604,9 @@
6586
6604
  len: bufferLen,
6587
6605
  start: bufferStart || 0,
6588
6606
  end: bufferEnd || 0,
6589
- nextStart: bufferStartNext,
6590
- buffered: buffered
6607
+ nextStart: nextStart,
6608
+ buffered: buffered,
6609
+ bufferedIndex: bufferedIndex
6591
6610
  };
6592
6611
  }
6593
6612
 
@@ -8866,7 +8885,6 @@
8866
8885
  // reset startPosition and lastCurrentTime to restart playback @ stream beginning
8867
8886
  _this.log("setting startPosition to 0 because media ended");
8868
8887
  _this.startPosition = _this.lastCurrentTime = 0;
8869
- _this.triggerEnded();
8870
8888
  };
8871
8889
  _this.playlistType = playlistType;
8872
8890
  _this.hls = hls;
@@ -9015,9 +9033,6 @@
9015
9033
  this.startFragRequested = false;
9016
9034
  };
9017
9035
  _proto.onError = function onError(event, data) {};
9018
- _proto.triggerEnded = function triggerEnded() {
9019
- /* overridden in stream-controller */
9020
- };
9021
9036
  _proto.onManifestLoaded = function onManifestLoaded(event, data) {
9022
9037
  this.startTimeOffset = data.startTimeOffset;
9023
9038
  };
@@ -10194,6 +10209,14 @@
10194
10209
  get: function get() {
10195
10210
  return this.buffering;
10196
10211
  }
10212
+ }, {
10213
+ key: "inFlightFrag",
10214
+ get: function get() {
10215
+ return {
10216
+ frag: this.fragCurrent,
10217
+ state: this.state
10218
+ };
10219
+ }
10197
10220
  }, {
10198
10221
  key: "state",
10199
10222
  get: function get() {
@@ -16311,7 +16334,7 @@
16311
16334
  return !remuxResult.audio && !remuxResult.video && !remuxResult.text && !remuxResult.id3 && !remuxResult.initSegment;
16312
16335
  }
16313
16336
 
16314
- var version = "1.6.0-beta.2.0.canary.10924";
16337
+ var version = "1.6.0-beta.2.0.canary.10925";
16315
16338
 
16316
16339
  // ensure the worker ends up in the bundle
16317
16340
  // If the worker should not be included this gets aliased to empty.js
@@ -16682,7 +16705,7 @@
16682
16705
  return TransmuxerInterface;
16683
16706
  }();
16684
16707
 
16685
- var TICK_INTERVAL$2 = 100; // how often to tick in ms
16708
+ var TICK_INTERVAL$3 = 100; // how often to tick in ms
16686
16709
  var AudioStreamController = /*#__PURE__*/function (_BaseStreamController) {
16687
16710
  function AudioStreamController(hls, fragmentTracker, keyLoader) {
16688
16711
  var _this;
@@ -16793,7 +16816,7 @@
16793
16816
  }
16794
16817
  var lastCurrentTime = this.lastCurrentTime;
16795
16818
  this.stopLoad();
16796
- this.setInterval(TICK_INTERVAL$2);
16819
+ this.setInterval(TICK_INTERVAL$3);
16797
16820
  if (lastCurrentTime > 0 && startPosition === -1) {
16798
16821
  this.log("Override startPosition with lastCurrentTime @" + lastCurrentTime.toFixed(3));
16799
16822
  startPosition = lastCurrentTime;
@@ -17025,7 +17048,7 @@
17025
17048
  this.flushAudioIfNeeded(data);
17026
17049
  if (this.state !== State.STOPPED) {
17027
17050
  // switching to audio track, start timer if not already started
17028
- this.setInterval(TICK_INTERVAL$2);
17051
+ this.setInterval(TICK_INTERVAL$3);
17029
17052
  this.state = State.IDLE;
17030
17053
  this.tick();
17031
17054
  }
@@ -18732,7 +18755,6 @@
18732
18755
  var sbTrack = transferredTrack != null && transferredTrack.buffer ? transferredTrack : track;
18733
18756
  var sbCodec = (sbTrack == null ? undefined : sbTrack.pendingCodec) || (sbTrack == null ? undefined : sbTrack.codec);
18734
18757
  var trackLevelCodec = sbTrack == null ? undefined : sbTrack.levelCodec;
18735
- var forceChangeType = !sbTrack || !!_this9.hls.config.assetPlayerId;
18736
18758
  if (!track) {
18737
18759
  track = tracks[trackName] = {
18738
18760
  buffer: undefined,
@@ -18749,7 +18771,7 @@
18749
18771
  var currentCodec = currentCodecFull == null ? undefined : currentCodecFull.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');
18750
18772
  var trackCodec = pickMostCompleteCodecName(codec, levelCodec);
18751
18773
  var nextCodec = (_trackCodec = trackCodec) == null ? undefined : _trackCodec.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');
18752
- if (trackCodec && (currentCodec !== nextCodec || forceChangeType)) {
18774
+ if (trackCodec && currentCodecFull && currentCodec !== nextCodec) {
18753
18775
  if (trackName.slice(0, 5) === 'audio') {
18754
18776
  trackCodec = getCodecCompatibleName(trackCodec, _this9.appendSource);
18755
18777
  }
@@ -23839,6 +23861,14 @@
23839
23861
  return AssetListLoader;
23840
23862
  }();
23841
23863
 
23864
+ function addEventListener(el, type, listener) {
23865
+ removeEventListener(el, type, listener);
23866
+ el.addEventListener(type, listener);
23867
+ }
23868
+ function removeEventListener(el, type, listener) {
23869
+ el.removeEventListener(type, listener);
23870
+ }
23871
+
23842
23872
  function playWithCatch(media) {
23843
23873
  media == null ? undefined : media.play().catch(function () {
23844
23874
  /* no-op */
@@ -24154,24 +24184,23 @@
24154
24184
  this.onScheduleUpdate = null;
24155
24185
  };
24156
24186
  _proto.onDestroying = function onDestroying() {
24157
- var media = this.primaryMedia;
24187
+ var media = this.primaryMedia || this.media;
24158
24188
  if (media) {
24159
24189
  this.removeMediaListeners(media);
24160
24190
  }
24161
24191
  };
24162
24192
  _proto.removeMediaListeners = function removeMediaListeners(media) {
24163
- media.removeEventListener('play', this.onPlay);
24164
- media.removeEventListener('pause', this.onPause);
24165
- media.removeEventListener('seeking', this.onSeeking);
24166
- media.removeEventListener('timeupdate', this.onTimeupdate);
24193
+ removeEventListener(media, 'play', this.onPlay);
24194
+ removeEventListener(media, 'pause', this.onPause);
24195
+ removeEventListener(media, 'seeking', this.onSeeking);
24196
+ removeEventListener(media, 'timeupdate', this.onTimeupdate);
24167
24197
  };
24168
24198
  _proto.onMediaAttaching = function onMediaAttaching(event, data) {
24169
24199
  var media = this.media = data.media;
24170
- this.removeMediaListeners(media);
24171
- media.addEventListener('seeking', this.onSeeking);
24172
- media.addEventListener('timeupdate', this.onTimeupdate);
24173
- media.addEventListener('play', this.onPlay);
24174
- media.addEventListener('pause', this.onPause);
24200
+ addEventListener(media, 'seeking', this.onSeeking);
24201
+ addEventListener(media, 'timeupdate', this.onTimeupdate);
24202
+ addEventListener(media, 'play', this.onPlay);
24203
+ addEventListener(media, 'pause', this.onPause);
24175
24204
  };
24176
24205
  _proto.onMediaAttached = function onMediaAttached(event, data) {
24177
24206
  var playingItem = this.playingItem;
@@ -25676,7 +25705,7 @@
25676
25705
  }]);
25677
25706
  }(Logger);
25678
25707
 
25679
- var TICK_INTERVAL$1 = 500; // how often to tick in ms
25708
+ var TICK_INTERVAL$2 = 500; // how often to tick in ms
25680
25709
 
25681
25710
  var SubtitleStreamController = /*#__PURE__*/function (_BaseStreamController) {
25682
25711
  function SubtitleStreamController(hls, fragmentTracker, keyLoader) {
@@ -25718,7 +25747,7 @@
25718
25747
  _proto.startLoad = function startLoad(startPosition) {
25719
25748
  this.stopLoad();
25720
25749
  this.state = State.IDLE;
25721
- this.setInterval(TICK_INTERVAL$1);
25750
+ this.setInterval(TICK_INTERVAL$2);
25722
25751
  this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition;
25723
25752
  this.tick();
25724
25753
  };
@@ -25854,7 +25883,7 @@
25854
25883
  this.mediaBuffer = null;
25855
25884
  }
25856
25885
  if (currentTrack && this.state !== State.STOPPED) {
25857
- this.setInterval(TICK_INTERVAL$1);
25886
+ this.setInterval(TICK_INTERVAL$2);
25858
25887
  }
25859
25888
  }
25860
25889
 
@@ -30255,16 +30284,20 @@
30255
30284
  frontBufferFlushThreshold: Infinity,
30256
30285
  maxBufferSize: 60 * 1000 * 1000,
30257
30286
  // used by stream-controller
30258
- maxBufferHole: 0.1,
30287
+ maxFragLookUpTolerance: 0.25,
30259
30288
  // used by stream-controller
30289
+ maxBufferHole: 0.1,
30290
+ // used by stream-controller and gap-controller
30291
+ detectStallWithCurrentTimeMs: 1250,
30292
+ // used by gap-controller
30260
30293
  highBufferWatchdogPeriod: 2,
30261
- // used by stream-controller
30294
+ // used by gap-controller
30262
30295
  nudgeOffset: 0.1,
30263
- // used by stream-controller
30296
+ // used by gap-controller
30264
30297
  nudgeMaxRetry: 3,
30265
- // used by stream-controller
30266
- maxFragLookUpTolerance: 0.25,
30267
- // used by stream-controller
30298
+ // used by gap-controller
30299
+ nudgeOnVideoHole: true,
30300
+ // used by gap-controller
30268
30301
  liveSyncDurationCount: 3,
30269
30302
  // used by latency-controller
30270
30303
  liveSyncOnStallIncrease: 1,
@@ -30363,7 +30396,6 @@
30363
30396
  progressive: false,
30364
30397
  lowLatencyMode: true,
30365
30398
  cmcd: undefined,
30366
- detectStallWithCurrentTimeMs: 1250,
30367
30399
  enableDateRangeMetadataCues: true,
30368
30400
  enableEmsgMetadataCues: true,
30369
30401
  enableEmsgKLVMetadata: false,
@@ -30618,6 +30650,534 @@
30618
30650
  }
30619
30651
  }
30620
30652
 
30653
+ var MAX_START_GAP_JUMP = 2.0;
30654
+ var SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
30655
+ var SKIP_BUFFER_RANGE_START = 0.05;
30656
+ var TICK_INTERVAL$1 = 100;
30657
+ var GapController = /*#__PURE__*/function (_TaskLoop) {
30658
+ function GapController(hls, fragmentTracker) {
30659
+ var _this;
30660
+ _this = _TaskLoop.call(this, 'gap-controller', hls.logger) || this;
30661
+ _this.hls = null;
30662
+ _this.fragmentTracker = null;
30663
+ _this.media = null;
30664
+ _this.mediaSource = undefined;
30665
+ _this.nudgeRetry = 0;
30666
+ _this.stallReported = false;
30667
+ _this.stalled = null;
30668
+ _this.moved = false;
30669
+ _this.seeking = false;
30670
+ _this.buffered = {};
30671
+ _this.lastCurrentTime = 0;
30672
+ _this.ended = 0;
30673
+ _this.waiting = 0;
30674
+ _this.onMediaPlaying = function () {
30675
+ _this.ended = 0;
30676
+ _this.waiting = 0;
30677
+ };
30678
+ _this.onMediaWaiting = function () {
30679
+ var _this$media;
30680
+ if ((_this$media = _this.media) != null && _this$media.seeking) {
30681
+ return;
30682
+ }
30683
+ _this.waiting = self.performance.now();
30684
+ _this.tick();
30685
+ };
30686
+ _this.onMediaEnded = function () {
30687
+ if (_this.hls) {
30688
+ var _this$media2;
30689
+ // ended is set when triggering MEDIA_ENDED so that we do not trigger it again on stall or on tick with media.ended
30690
+ _this.ended = ((_this$media2 = _this.media) == null ? undefined : _this$media2.currentTime) || 1;
30691
+ _this.hls.trigger(Events.MEDIA_ENDED, {
30692
+ stalled: false
30693
+ });
30694
+ }
30695
+ };
30696
+ _this.hls = hls;
30697
+ _this.fragmentTracker = fragmentTracker;
30698
+ _this.registerListeners();
30699
+ return _this;
30700
+ }
30701
+ _inheritsLoose(GapController, _TaskLoop);
30702
+ var _proto = GapController.prototype;
30703
+ _proto.registerListeners = function registerListeners() {
30704
+ var hls = this.hls;
30705
+ if (hls) {
30706
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
30707
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
30708
+ hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
30709
+ }
30710
+ };
30711
+ _proto.unregisterListeners = function unregisterListeners() {
30712
+ var hls = this.hls;
30713
+ if (hls) {
30714
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
30715
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
30716
+ hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
30717
+ }
30718
+ };
30719
+ _proto.destroy = function destroy() {
30720
+ _TaskLoop.prototype.destroy.call(this);
30721
+ this.unregisterListeners();
30722
+ this.media = this.hls = this.fragmentTracker = null;
30723
+ this.mediaSource = undefined;
30724
+ };
30725
+ _proto.onMediaAttached = function onMediaAttached(event, data) {
30726
+ this.setInterval(TICK_INTERVAL$1);
30727
+ this.mediaSource = data.mediaSource;
30728
+ var media = this.media = data.media;
30729
+ addEventListener(media, 'playing', this.onMediaPlaying);
30730
+ addEventListener(media, 'waiting', this.onMediaWaiting);
30731
+ addEventListener(media, 'ended', this.onMediaEnded);
30732
+ };
30733
+ _proto.onMediaDetaching = function onMediaDetaching(event, data) {
30734
+ this.clearInterval();
30735
+ var media = this.media;
30736
+ if (media) {
30737
+ removeEventListener(media, 'playing', this.onMediaPlaying);
30738
+ removeEventListener(media, 'waiting', this.onMediaWaiting);
30739
+ removeEventListener(media, 'ended', this.onMediaEnded);
30740
+ this.media = null;
30741
+ }
30742
+ this.mediaSource = undefined;
30743
+ };
30744
+ _proto.onBufferAppended = function onBufferAppended(event, data) {
30745
+ this.buffered = data.timeRanges;
30746
+ };
30747
+ _proto.tick = function tick() {
30748
+ var _this$media3;
30749
+ if (!((_this$media3 = this.media) != null && _this$media3.readyState) || !this.hasBuffered) {
30750
+ return;
30751
+ }
30752
+ var currentTime = this.media.currentTime;
30753
+ this.poll(currentTime, this.lastCurrentTime);
30754
+ this.lastCurrentTime = currentTime;
30755
+ }
30756
+
30757
+ /**
30758
+ * Checks if the playhead is stuck within a gap, and if so, attempts to free it.
30759
+ * A gap is an unbuffered range between two buffered ranges (or the start and the first buffered range).
30760
+ *
30761
+ * @param lastCurrentTime - Previously read playhead position
30762
+ */;
30763
+ _proto.poll = function poll(currentTime, lastCurrentTime) {
30764
+ var _this$hls, _this$hls2;
30765
+ var config = (_this$hls = this.hls) == null ? undefined : _this$hls.config;
30766
+ if (!config) {
30767
+ return;
30768
+ }
30769
+ var media = this.media,
30770
+ stalled = this.stalled;
30771
+ if (!media) {
30772
+ return;
30773
+ }
30774
+ var seeking = media.seeking;
30775
+ var seeked = this.seeking && !seeking;
30776
+ var beginSeek = !this.seeking && seeking;
30777
+ var pausedEndedOrHalted = media.paused && !seeking || media.ended || media.playbackRate === 0;
30778
+ this.seeking = seeking;
30779
+
30780
+ // The playhead is moving, no-op
30781
+ if (currentTime !== lastCurrentTime) {
30782
+ if (lastCurrentTime) {
30783
+ this.ended = 0;
30784
+ }
30785
+ this.moved = true;
30786
+ if (!seeking) {
30787
+ this.nudgeRetry = 0;
30788
+ // When crossing between buffered video time ranges, but not audio, flush pipeline with seek (Chrome)
30789
+ if (config.nudgeOnVideoHole && !pausedEndedOrHalted && currentTime > lastCurrentTime) {
30790
+ this.nudgeOnVideoHole(currentTime, lastCurrentTime);
30791
+ }
30792
+ }
30793
+ if (this.waiting === 0) {
30794
+ this.stallResolved(currentTime);
30795
+ }
30796
+ return;
30797
+ }
30798
+
30799
+ // Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek
30800
+ if (beginSeek || seeked) {
30801
+ if (seeked) {
30802
+ this.stallResolved(currentTime);
30803
+ }
30804
+ return;
30805
+ }
30806
+
30807
+ // The playhead should not be moving
30808
+ if (pausedEndedOrHalted) {
30809
+ this.nudgeRetry = 0;
30810
+ this.stallResolved(currentTime);
30811
+ // Fire MEDIA_ENDED to workaround event not being dispatched by browser
30812
+ if (!this.ended && media.ended && this.hls) {
30813
+ this.ended = currentTime || 1;
30814
+ this.hls.trigger(Events.MEDIA_ENDED, {
30815
+ stalled: false
30816
+ });
30817
+ }
30818
+ return;
30819
+ }
30820
+ if (!BufferHelper.getBuffered(media).length) {
30821
+ this.nudgeRetry = 0;
30822
+ return;
30823
+ }
30824
+
30825
+ // Resolve stalls at buffer holes using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
30826
+ var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
30827
+ var nextStart = bufferInfo.nextStart || 0;
30828
+ var fragmentTracker = this.fragmentTracker;
30829
+ if (seeking && fragmentTracker && this.hls) {
30830
+ // Is there a fragment loading/parsing/appending before currentTime?
30831
+ var inFlightDependency = getInFlightDependency(this.hls.inFlightFragments, currentTime);
30832
+
30833
+ // Waiting for seeking in a buffered range to complete
30834
+ var hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
30835
+ // Next buffered range is too far ahead to jump to while still seeking
30836
+ var noBufferHole = !nextStart || inFlightDependency || nextStart - currentTime > MAX_START_GAP_JUMP && !fragmentTracker.getPartialFragment(currentTime);
30837
+ if (hasEnoughBuffer || noBufferHole) {
30838
+ return;
30839
+ }
30840
+ // Reset moved state when seeking to a point in or before a gap/hole
30841
+ this.moved = false;
30842
+ }
30843
+
30844
+ // Skip start gaps if we haven't played, but the last poll detected the start of a stall
30845
+ // The addition poll gives the browser a chance to jump the gap for us
30846
+ var levelDetails = (_this$hls2 = this.hls) == null ? undefined : _this$hls2.latestLevelDetails;
30847
+ if (!this.moved && this.stalled !== null && fragmentTracker) {
30848
+ // There is no playable buffer (seeked, waiting for buffer)
30849
+ var isBuffered = bufferInfo.len > 0;
30850
+ if (!isBuffered && !nextStart) {
30851
+ return;
30852
+ }
30853
+ // Jump start gaps within jump threshold
30854
+ var startJump = Math.max(nextStart, bufferInfo.start || 0) - currentTime;
30855
+
30856
+ // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
30857
+ // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
30858
+ // that begins over 1 target duration after the video start position.
30859
+ var isLive = !!(levelDetails != null && levelDetails.live);
30860
+ var maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
30861
+ var partialOrGap = fragmentTracker.getPartialFragment(currentTime);
30862
+ if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
30863
+ if (!media.paused) {
30864
+ this._trySkipBufferHole(partialOrGap);
30865
+ }
30866
+ return;
30867
+ }
30868
+ }
30869
+
30870
+ // Start tracking stall time
30871
+ var detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs;
30872
+ var tnow = self.performance.now();
30873
+ var tWaiting = this.waiting;
30874
+ if (stalled === null) {
30875
+ // Use time of recent "waiting" event
30876
+ if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) {
30877
+ this.stalled = tWaiting;
30878
+ } else {
30879
+ this.stalled = tnow;
30880
+ }
30881
+ return;
30882
+ }
30883
+ var stalledDuration = tnow - stalled;
30884
+ if (!seeking && (stalledDuration >= detectStallWithCurrentTimeMs || tWaiting) && this.hls) {
30885
+ var _this$mediaSource;
30886
+ // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
30887
+ if (((_this$mediaSource = this.mediaSource) == null ? undefined : _this$mediaSource.readyState) === 'ended' && !(levelDetails != null && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? undefined : levelDetails.edge) || 0)) < 1) {
30888
+ if (this.ended) {
30889
+ return;
30890
+ }
30891
+ this.ended = currentTime || 1;
30892
+ this.hls.trigger(Events.MEDIA_ENDED, {
30893
+ stalled: true
30894
+ });
30895
+ return;
30896
+ }
30897
+ // Report stalling after trying to fix
30898
+ this._reportStall(bufferInfo);
30899
+ if (!this.media || !this.hls) {
30900
+ return;
30901
+ }
30902
+ }
30903
+ var bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole);
30904
+ this._tryFixBufferStall(bufferedWithHoles, stalledDuration);
30905
+ };
30906
+ _proto.stallResolved = function stallResolved(currentTime) {
30907
+ var stalled = this.stalled;
30908
+ if (stalled && this.hls) {
30909
+ this.stalled = null;
30910
+ // The playhead is now moving, but was previously stalled
30911
+ if (this.stallReported) {
30912
+ var stalledDuration = self.performance.now() - stalled;
30913
+ this.log("playback not stuck anymore @" + currentTime + ", after " + Math.round(stalledDuration) + "ms");
30914
+ this.stallReported = false;
30915
+ this.waiting = 0;
30916
+ this.hls.trigger(Events.STALL_RESOLVED, {});
30917
+ }
30918
+ }
30919
+ };
30920
+ _proto.nudgeOnVideoHole = function nudgeOnVideoHole(currentTime, lastCurrentTime) {
30921
+ var _this$buffered$audio;
30922
+ // Chrome will play one second past a hole in video buffered time ranges without rendering any video from the subsequent range and then stall as long as audio is buffered:
30923
+ // https://github.com/video-dev/hls.js/issues/5631
30924
+ // https://issues.chromium.org/issues/40280613#comment10
30925
+ // Detect the potential for this situation and proactively seek to flush the video pipeline once the playhead passes the start of the video hole.
30926
+ // When there are audio and video buffers and currentTime is past the end of the first video buffered range...
30927
+ var videoSourceBuffered = this.buffered.video;
30928
+ if (this.hls && this.media && this.fragmentTracker && (_this$buffered$audio = this.buffered.audio) != null && _this$buffered$audio.length && videoSourceBuffered && videoSourceBuffered.length > 1 && currentTime > videoSourceBuffered.end(0)) {
30929
+ // and audio is buffered at the playhead
30930
+ var audioBufferInfo = BufferHelper.bufferedInfo(BufferHelper.timeRangesToArray(this.buffered.audio), currentTime, 0);
30931
+ if (audioBufferInfo.len > 1 && lastCurrentTime >= audioBufferInfo.start) {
30932
+ var videoTimes = BufferHelper.timeRangesToArray(videoSourceBuffered);
30933
+ var lastBufferedIndex = BufferHelper.bufferedInfo(videoTimes, lastCurrentTime, 0).bufferedIndex;
30934
+ // nudge when crossing into another video buffered range (hole).
30935
+ if (lastBufferedIndex > -1 && lastBufferedIndex < videoTimes.length - 1) {
30936
+ var bufferedIndex = BufferHelper.bufferedInfo(videoTimes, currentTime, 0).bufferedIndex;
30937
+ var holeStart = videoTimes[lastBufferedIndex].end;
30938
+ var holeEnd = videoTimes[lastBufferedIndex + 1].start;
30939
+ if ((bufferedIndex === -1 || bufferedIndex > lastBufferedIndex) && holeEnd - holeStart < 1 &&
30940
+ // `maxBufferHole` may be too small and setting it to 0 should not disable this feature
30941
+ currentTime - holeStart < 2) {
30942
+ var error = new Error("nudging playhead to flush pipeline after video hole. currentTime: " + currentTime + " hole: " + holeStart + " -> " + holeEnd + " buffered index: " + bufferedIndex);
30943
+ this.warn(error.message);
30944
+ // Magic number to flush the pipeline without interuption to audio playback:
30945
+ this.media.currentTime += 0.000001;
30946
+ var frag = this.fragmentTracker.getPartialFragment(currentTime) || undefined;
30947
+ var bufferInfo = BufferHelper.bufferInfo(this.media, currentTime, 0);
30948
+ this.hls.trigger(Events.ERROR, {
30949
+ type: ErrorTypes.MEDIA_ERROR,
30950
+ details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
30951
+ fatal: false,
30952
+ error: error,
30953
+ reason: error.message,
30954
+ frag: frag,
30955
+ buffer: bufferInfo.len,
30956
+ bufferInfo: bufferInfo
30957
+ });
30958
+ }
30959
+ }
30960
+ }
30961
+ }
30962
+ }
30963
+
30964
+ /**
30965
+ * Detects and attempts to fix known buffer stalling issues.
30966
+ * @param bufferInfo - The properties of the current buffer.
30967
+ * @param stalledDurationMs - The amount of time Hls.js has been stalling for.
30968
+ * @private
30969
+ */;
30970
+ _proto._tryFixBufferStall = function _tryFixBufferStall(bufferInfo, stalledDurationMs) {
30971
+ var _this$hls3;
30972
+ var fragmentTracker = this.fragmentTracker,
30973
+ media = this.media;
30974
+ var config = (_this$hls3 = this.hls) == null ? undefined : _this$hls3.config;
30975
+ if (!media || !fragmentTracker || !config) {
30976
+ return;
30977
+ }
30978
+ var currentTime = media.currentTime;
30979
+ var partial = fragmentTracker.getPartialFragment(currentTime);
30980
+ if (partial) {
30981
+ // Try to skip over the buffer hole caused by a partial fragment
30982
+ // This method isn't limited by the size of the gap between buffered ranges
30983
+ var targetTime = this._trySkipBufferHole(partial);
30984
+ // we return here in this case, meaning
30985
+ // the branch below only executes when we haven't seeked to a new position
30986
+ if (targetTime || !this.media) {
30987
+ return;
30988
+ }
30989
+ }
30990
+
30991
+ // if we haven't had to skip over a buffer hole of a partial fragment
30992
+ // we may just have to "nudge" the playlist as the browser decoding/rendering engine
30993
+ // needs to cross some sort of threshold covering all source-buffers content
30994
+ // to start playing properly.
30995
+ var bufferedRanges = bufferInfo.buffered;
30996
+ if ((bufferedRanges && bufferedRanges.length > 1 && bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && (stalledDurationMs > config.highBufferWatchdogPeriod * 1000 || this.waiting)) {
30997
+ this.warn('Trying to nudge playhead over buffer-hole');
30998
+ // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
30999
+ // We only try to jump the hole if it's under the configured size
31000
+ this._tryNudgeBuffer(bufferInfo);
31001
+ }
31002
+ }
31003
+
31004
+ /**
31005
+ * Triggers a BUFFER_STALLED_ERROR event, but only once per stall period.
31006
+ * @param bufferLen - The playhead distance from the end of the current buffer segment.
31007
+ * @private
31008
+ */;
31009
+ _proto._reportStall = function _reportStall(bufferInfo) {
31010
+ var hls = this.hls,
31011
+ media = this.media,
31012
+ stallReported = this.stallReported,
31013
+ stalled = this.stalled;
31014
+ if (!stallReported && stalled !== null && media && hls) {
31015
+ // Report stalled error once
31016
+ this.stallReported = true;
31017
+ var error = new Error("Playback stalling at @" + media.currentTime + " due to low buffer (" + JSON.stringify(bufferInfo) + ")");
31018
+ this.warn(error.message);
31019
+ hls.trigger(Events.ERROR, {
31020
+ type: ErrorTypes.MEDIA_ERROR,
31021
+ details: ErrorDetails.BUFFER_STALLED_ERROR,
31022
+ fatal: false,
31023
+ error: error,
31024
+ buffer: bufferInfo.len,
31025
+ bufferInfo: bufferInfo,
31026
+ stalled: {
31027
+ start: stalled
31028
+ }
31029
+ });
31030
+ }
31031
+ }
31032
+
31033
+ /**
31034
+ * Attempts to fix buffer stalls by jumping over known gaps caused by partial fragments
31035
+ * @param partial - The partial fragment found at the current time (where playback is stalling).
31036
+ * @private
31037
+ */;
31038
+ _proto._trySkipBufferHole = function _trySkipBufferHole(partial) {
31039
+ var _this$hls4;
31040
+ var fragmentTracker = this.fragmentTracker,
31041
+ media = this.media;
31042
+ var config = (_this$hls4 = this.hls) == null ? undefined : _this$hls4.config;
31043
+ if (!media || !fragmentTracker || !config) {
31044
+ return 0;
31045
+ }
31046
+
31047
+ // Check if currentTime is between unbuffered regions of partial fragments
31048
+ var currentTime = media.currentTime;
31049
+ var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
31050
+ var startTime = currentTime < bufferInfo.start ? bufferInfo.start : bufferInfo.nextStart;
31051
+ if (startTime) {
31052
+ var bufferStarved = bufferInfo.len <= config.maxBufferHole;
31053
+ var waiting = bufferInfo.len > 0 && bufferInfo.len < 1 && media.readyState < 3;
31054
+ var gapLength = startTime - currentTime;
31055
+ if (gapLength > 0 && (bufferStarved || waiting)) {
31056
+ // Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial
31057
+ if (gapLength > config.maxBufferHole) {
31058
+ var startGap = false;
31059
+ if (currentTime === 0) {
31060
+ var startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN);
31061
+ if (startFrag && startTime < startFrag.end) {
31062
+ startGap = true;
31063
+ }
31064
+ }
31065
+ if (!startGap) {
31066
+ var startProvisioned = partial || fragmentTracker.getAppendedFrag(currentTime, PlaylistLevelType.MAIN);
31067
+ if (startProvisioned) {
31068
+ var moreToLoad = false;
31069
+ var pos = startProvisioned.end;
31070
+ while (pos < startTime) {
31071
+ var provisioned = fragmentTracker.getPartialFragment(pos);
31072
+ if (provisioned) {
31073
+ pos += provisioned.duration;
31074
+ } else {
31075
+ moreToLoad = true;
31076
+ break;
31077
+ }
31078
+ }
31079
+ if (moreToLoad) {
31080
+ return 0;
31081
+ }
31082
+ }
31083
+ }
31084
+ }
31085
+ var targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
31086
+ this.warn("skipping hole, adjusting currentTime from " + currentTime + " to " + targetTime);
31087
+ this.moved = true;
31088
+ media.currentTime = targetTime;
31089
+ if (!(partial != null && partial.gap) && this.hls) {
31090
+ var error = new Error("fragment loaded with buffer holes, seeking from " + currentTime + " to " + targetTime);
31091
+ this.hls.trigger(Events.ERROR, {
31092
+ type: ErrorTypes.MEDIA_ERROR,
31093
+ details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
31094
+ fatal: false,
31095
+ error: error,
31096
+ reason: error.message,
31097
+ frag: partial || undefined,
31098
+ buffer: bufferInfo.len,
31099
+ bufferInfo: bufferInfo
31100
+ });
31101
+ }
31102
+ return targetTime;
31103
+ }
31104
+ }
31105
+ return 0;
31106
+ }
31107
+
31108
+ /**
31109
+ * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount.
31110
+ * @private
31111
+ */;
31112
+ _proto._tryNudgeBuffer = function _tryNudgeBuffer(bufferInfo) {
31113
+ var hls = this.hls,
31114
+ media = this.media,
31115
+ nudgeRetry = this.nudgeRetry;
31116
+ var config = hls == null ? undefined : hls.config;
31117
+ if (!media || !config) {
31118
+ return 0;
31119
+ }
31120
+ var currentTime = media.currentTime;
31121
+ this.nudgeRetry++;
31122
+ if (nudgeRetry < config.nudgeMaxRetry) {
31123
+ var targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
31124
+ // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
31125
+ var error = new Error("Nudging 'currentTime' from " + currentTime + " to " + targetTime);
31126
+ this.warn(error.message);
31127
+ media.currentTime = targetTime;
31128
+ hls.trigger(Events.ERROR, {
31129
+ type: ErrorTypes.MEDIA_ERROR,
31130
+ details: ErrorDetails.BUFFER_NUDGE_ON_STALL,
31131
+ error: error,
31132
+ fatal: false,
31133
+ buffer: bufferInfo.len,
31134
+ bufferInfo: bufferInfo
31135
+ });
31136
+ } else {
31137
+ var _error = new Error("Playhead still not moving while enough data buffered @" + currentTime + " after " + config.nudgeMaxRetry + " nudges");
31138
+ this.error(_error.message);
31139
+ hls.trigger(Events.ERROR, {
31140
+ type: ErrorTypes.MEDIA_ERROR,
31141
+ details: ErrorDetails.BUFFER_STALLED_ERROR,
31142
+ error: _error,
31143
+ fatal: true,
31144
+ buffer: bufferInfo.len,
31145
+ bufferInfo: bufferInfo
31146
+ });
31147
+ }
31148
+ };
31149
+ return _createClass(GapController, [{
31150
+ key: "hasBuffered",
31151
+ get: function get() {
31152
+ return Object.keys(this.buffered).length > 0;
31153
+ }
31154
+ }]);
31155
+ }(TaskLoop);
31156
+ function getInFlightDependency(inFlightFragments, currentTime) {
31157
+ var main = inFlight(inFlightFragments.main);
31158
+ if (main && main.start <= currentTime) {
31159
+ return main;
31160
+ }
31161
+ var audio = inFlight(inFlightFragments.audio);
31162
+ if (audio && audio.start <= currentTime) {
31163
+ return audio;
31164
+ }
31165
+ return null;
31166
+ }
31167
+ function inFlight(inFlightData) {
31168
+ if (!inFlightData) {
31169
+ return null;
31170
+ }
31171
+ switch (inFlightData.state) {
31172
+ case State.IDLE:
31173
+ case State.STOPPED:
31174
+ case State.ENDED:
31175
+ case State.ERROR:
31176
+ return null;
31177
+ }
31178
+ return inFlightData.frag;
31179
+ }
31180
+
30621
31181
  var MIN_CUE_DURATION = 0.25;
30622
31182
  function getCueClass() {
30623
31183
  if (typeof self === 'undefined') return undefined;
@@ -31789,375 +32349,6 @@
31789
32349
  });
31790
32350
  }
31791
32351
 
31792
- var MAX_START_GAP_JUMP = 2.0;
31793
- var SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
31794
- var SKIP_BUFFER_RANGE_START = 0.05;
31795
- var GapController = /*#__PURE__*/function (_Logger) {
31796
- function GapController(media, fragmentTracker, hls) {
31797
- var _this;
31798
- _this = _Logger.call(this, 'gap-controller', hls.logger) || this;
31799
- _this.media = null;
31800
- _this.fragmentTracker = null;
31801
- _this.hls = null;
31802
- _this.nudgeRetry = 0;
31803
- _this.stallReported = false;
31804
- _this.stalled = null;
31805
- _this.moved = false;
31806
- _this.seeking = false;
31807
- _this.ended = 0;
31808
- _this.waiting = 0;
31809
- _this.media = media;
31810
- _this.fragmentTracker = fragmentTracker;
31811
- _this.hls = hls;
31812
- return _this;
31813
- }
31814
- _inheritsLoose(GapController, _Logger);
31815
- var _proto = GapController.prototype;
31816
- _proto.destroy = function destroy() {
31817
- this.media = this.hls = this.fragmentTracker = null;
31818
- }
31819
-
31820
- /**
31821
- * Checks if the playhead is stuck within a gap, and if so, attempts to free it.
31822
- * A gap is an unbuffered range between two buffered ranges (or the start and the first buffered range).
31823
- *
31824
- * @param lastCurrentTime - Previously read playhead position
31825
- */;
31826
- _proto.poll = function poll(lastCurrentTime, activeFrag, levelDetails, state) {
31827
- var _this$hls;
31828
- var media = this.media,
31829
- stalled = this.stalled;
31830
- if (!media) {
31831
- return;
31832
- }
31833
- var currentTime = media.currentTime,
31834
- seeking = media.seeking;
31835
- var seeked = this.seeking && !seeking;
31836
- var beginSeek = !this.seeking && seeking;
31837
- this.seeking = seeking;
31838
-
31839
- // The playhead is moving, no-op
31840
- if (currentTime !== lastCurrentTime) {
31841
- if (lastCurrentTime) {
31842
- this.ended = 0;
31843
- }
31844
- this.moved = true;
31845
- if (!seeking) {
31846
- this.nudgeRetry = 0;
31847
- }
31848
- if (this.waiting === 0) {
31849
- this.stallResolved(currentTime);
31850
- }
31851
- return;
31852
- }
31853
-
31854
- // Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek
31855
- if (beginSeek || seeked) {
31856
- if (seeked) {
31857
- this.stallResolved(currentTime);
31858
- }
31859
- return;
31860
- }
31861
-
31862
- // The playhead should not be moving
31863
- if (media.paused && !seeking || media.ended || media.playbackRate === 0) {
31864
- this.nudgeRetry = 0;
31865
- this.stallResolved(currentTime);
31866
- // Fire MEDIA_ENDED to workaround event not being dispatched by browser
31867
- if (!this.ended && media.ended && this.hls) {
31868
- this.ended = currentTime || 1;
31869
- this.hls.trigger(Events.MEDIA_ENDED, {
31870
- stalled: false
31871
- });
31872
- }
31873
- return;
31874
- }
31875
- if (!BufferHelper.getBuffered(media).length) {
31876
- this.nudgeRetry = 0;
31877
- return;
31878
- }
31879
- var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
31880
- var nextStart = bufferInfo.nextStart || 0;
31881
- var fragmentTracker = this.fragmentTracker;
31882
- if (seeking && fragmentTracker) {
31883
- // Waiting for seeking in a buffered range to complete
31884
- var hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
31885
- // 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 && !fragmentTracker.getPartialFragment(currentTime);
31887
- if (hasEnoughBuffer || noBufferGap) {
31888
- return;
31889
- }
31890
- // Reset moved state when seeking to a point in or before a gap
31891
- this.moved = false;
31892
- }
31893
-
31894
- // Skip start gaps if we haven't played, but the last poll detected the start of a stall
31895
- // The addition poll gives the browser a chance to jump the gap for us
31896
- if (!this.moved && this.stalled !== null && fragmentTracker) {
31897
- // There is no playable buffer (seeked, waiting for buffer)
31898
- var isBuffered = bufferInfo.len > 0;
31899
- if (!isBuffered && !nextStart) {
31900
- return;
31901
- }
31902
- // Jump start gaps within jump threshold
31903
- var startJump = Math.max(nextStart, bufferInfo.start || 0) - currentTime;
31904
-
31905
- // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
31906
- // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
31907
- // that begins over 1 target duration after the video start position.
31908
- var isLive = !!(levelDetails != null && levelDetails.live);
31909
- var maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
31910
- var partialOrGap = fragmentTracker.getPartialFragment(currentTime);
31911
- if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
31912
- if (!media.paused) {
31913
- this._trySkipBufferHole(partialOrGap);
31914
- }
31915
- return;
31916
- }
31917
- }
31918
-
31919
- // Start tracking stall time
31920
- var config = (_this$hls = this.hls) == null ? undefined : _this$hls.config;
31921
- if (!config) {
31922
- return;
31923
- }
31924
- var detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs;
31925
- var tnow = self.performance.now();
31926
- var tWaiting = this.waiting;
31927
- if (stalled === null) {
31928
- // Use time of recent "waiting" event
31929
- if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) {
31930
- this.stalled = tWaiting;
31931
- } else {
31932
- this.stalled = tnow;
31933
- }
31934
- return;
31935
- }
31936
- var stalledDuration = tnow - stalled;
31937
- if (!seeking && (stalledDuration >= detectStallWithCurrentTimeMs || tWaiting) && this.hls) {
31938
- // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
31939
- if (state === State.ENDED && !(levelDetails != null && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? undefined : levelDetails.edge) || 0)) < 1) {
31940
- if (this.ended) {
31941
- return;
31942
- }
31943
- this.ended = currentTime || 1;
31944
- this.hls.trigger(Events.MEDIA_ENDED, {
31945
- stalled: true
31946
- });
31947
- return;
31948
- }
31949
- // Report stalling after trying to fix
31950
- this._reportStall(bufferInfo);
31951
- if (!this.media || !this.hls) {
31952
- return;
31953
- }
31954
- }
31955
- var bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole);
31956
- this._tryFixBufferStall(bufferedWithHoles, stalledDuration);
31957
- };
31958
- _proto.stallResolved = function stallResolved(currentTime) {
31959
- var stalled = this.stalled;
31960
- if (stalled && this.hls) {
31961
- this.stalled = null;
31962
- // The playhead is now moving, but was previously stalled
31963
- if (this.stallReported) {
31964
- var stalledDuration = self.performance.now() - stalled;
31965
- this.warn("playback not stuck anymore @" + currentTime + ", after " + Math.round(stalledDuration) + "ms");
31966
- this.stallReported = false;
31967
- this.waiting = 0;
31968
- this.hls.trigger(Events.STALL_RESOLVED, {});
31969
- }
31970
- }
31971
- }
31972
-
31973
- /**
31974
- * Detects and attempts to fix known buffer stalling issues.
31975
- * @param bufferInfo - The properties of the current buffer.
31976
- * @param stalledDurationMs - The amount of time Hls.js has been stalling for.
31977
- * @private
31978
- */;
31979
- _proto._tryFixBufferStall = function _tryFixBufferStall(bufferInfo, stalledDurationMs) {
31980
- var _this$hls2;
31981
- var fragmentTracker = this.fragmentTracker,
31982
- media = this.media;
31983
- var config = (_this$hls2 = this.hls) == null ? undefined : _this$hls2.config;
31984
- if (!media || !fragmentTracker || !config) {
31985
- return;
31986
- }
31987
- var currentTime = media.currentTime;
31988
- var partial = fragmentTracker.getPartialFragment(currentTime);
31989
- if (partial) {
31990
- // Try to skip over the buffer hole caused by a partial fragment
31991
- // This method isn't limited by the size of the gap between buffered ranges
31992
- var targetTime = this._trySkipBufferHole(partial);
31993
- // we return here in this case, meaning
31994
- // the branch below only executes when we haven't seeked to a new position
31995
- if (targetTime || !this.media) {
31996
- return;
31997
- }
31998
- }
31999
-
32000
- // if we haven't had to skip over a buffer hole of a partial fragment
32001
- // we may just have to "nudge" the playlist as the browser decoding/rendering engine
32002
- // needs to cross some sort of threshold covering all source-buffers content
32003
- // to start playing properly.
32004
- var bufferedRanges = bufferInfo.buffered;
32005
- if ((bufferedRanges && bufferedRanges.length > 1 && bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
32006
- this.warn('Trying to nudge playhead over buffer-hole');
32007
- // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
32008
- // We only try to jump the hole if it's under the configured size
32009
- this._tryNudgeBuffer(bufferInfo);
32010
- }
32011
- }
32012
-
32013
- /**
32014
- * Triggers a BUFFER_STALLED_ERROR event, but only once per stall period.
32015
- * @param bufferLen - The playhead distance from the end of the current buffer segment.
32016
- * @private
32017
- */;
32018
- _proto._reportStall = function _reportStall(bufferInfo) {
32019
- var hls = this.hls,
32020
- media = this.media,
32021
- stallReported = this.stallReported,
32022
- stalled = this.stalled;
32023
- if (!stallReported && stalled !== null && media && hls) {
32024
- // Report stalled error once
32025
- this.stallReported = true;
32026
- var error = new Error("Playback stalling at @" + media.currentTime + " due to low buffer (" + JSON.stringify(bufferInfo) + ")");
32027
- this.warn(error.message);
32028
- hls.trigger(Events.ERROR, {
32029
- type: ErrorTypes.MEDIA_ERROR,
32030
- details: ErrorDetails.BUFFER_STALLED_ERROR,
32031
- fatal: false,
32032
- error: error,
32033
- buffer: bufferInfo.len,
32034
- bufferInfo: bufferInfo,
32035
- stalled: {
32036
- start: stalled
32037
- }
32038
- });
32039
- }
32040
- }
32041
-
32042
- /**
32043
- * Attempts to fix buffer stalls by jumping over known gaps caused by partial fragments
32044
- * @param partial - The partial fragment found at the current time (where playback is stalling).
32045
- * @private
32046
- */;
32047
- _proto._trySkipBufferHole = function _trySkipBufferHole(partial) {
32048
- var _this$hls3;
32049
- var fragmentTracker = this.fragmentTracker,
32050
- media = this.media;
32051
- var config = (_this$hls3 = this.hls) == null ? undefined : _this$hls3.config;
32052
- if (!media || !fragmentTracker || !config) {
32053
- return 0;
32054
- }
32055
-
32056
- // Check if currentTime is between unbuffered regions of partial fragments
32057
- var currentTime = media.currentTime;
32058
- var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
32059
- var startTime = currentTime < bufferInfo.start ? bufferInfo.start : bufferInfo.nextStart;
32060
- if (startTime) {
32061
- var bufferStarved = bufferInfo.len <= config.maxBufferHole;
32062
- var waiting = bufferInfo.len > 0 && bufferInfo.len < 1 && media.readyState < 3;
32063
- var gapLength = startTime - currentTime;
32064
- if (gapLength > 0 && (bufferStarved || waiting)) {
32065
- // Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial
32066
- if (gapLength > config.maxBufferHole) {
32067
- var startGap = false;
32068
- if (currentTime === 0) {
32069
- var startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN);
32070
- if (startFrag && startTime < startFrag.end) {
32071
- startGap = true;
32072
- }
32073
- }
32074
- if (!startGap) {
32075
- var startProvisioned = partial || fragmentTracker.getAppendedFrag(currentTime, PlaylistLevelType.MAIN);
32076
- if (startProvisioned) {
32077
- var moreToLoad = false;
32078
- var pos = startProvisioned.end;
32079
- while (pos < startTime) {
32080
- var provisioned = fragmentTracker.getPartialFragment(pos);
32081
- if (provisioned) {
32082
- pos += provisioned.duration;
32083
- } else {
32084
- moreToLoad = true;
32085
- break;
32086
- }
32087
- }
32088
- if (moreToLoad) {
32089
- return 0;
32090
- }
32091
- }
32092
- }
32093
- }
32094
- var targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
32095
- this.warn("skipping hole, adjusting currentTime from " + currentTime + " to " + targetTime);
32096
- this.moved = true;
32097
- media.currentTime = targetTime;
32098
- if (partial && !partial.gap && this.hls) {
32099
- var error = new Error("fragment loaded with buffer holes, seeking from " + currentTime + " to " + targetTime);
32100
- this.hls.trigger(Events.ERROR, {
32101
- type: ErrorTypes.MEDIA_ERROR,
32102
- details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
32103
- fatal: false,
32104
- error: error,
32105
- reason: error.message,
32106
- frag: partial,
32107
- buffer: bufferInfo.len,
32108
- bufferInfo: bufferInfo
32109
- });
32110
- }
32111
- return targetTime;
32112
- }
32113
- }
32114
- return 0;
32115
- }
32116
-
32117
- /**
32118
- * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount.
32119
- * @private
32120
- */;
32121
- _proto._tryNudgeBuffer = function _tryNudgeBuffer(bufferInfo) {
32122
- var hls = this.hls,
32123
- media = this.media,
32124
- nudgeRetry = this.nudgeRetry;
32125
- var config = hls == null ? undefined : hls.config;
32126
- if (!media || !config) {
32127
- return 0;
32128
- }
32129
- var currentTime = media.currentTime;
32130
- this.nudgeRetry++;
32131
- if (nudgeRetry < config.nudgeMaxRetry) {
32132
- var targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
32133
- // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
32134
- var error = new Error("Nudging 'currentTime' from " + currentTime + " to " + targetTime);
32135
- this.warn(error.message);
32136
- media.currentTime = targetTime;
32137
- hls.trigger(Events.ERROR, {
32138
- type: ErrorTypes.MEDIA_ERROR,
32139
- details: ErrorDetails.BUFFER_NUDGE_ON_STALL,
32140
- error: error,
32141
- fatal: false,
32142
- buffer: bufferInfo.len,
32143
- bufferInfo: bufferInfo
32144
- });
32145
- } else {
32146
- var _error = new Error("Playhead still not moving while enough data buffered @" + currentTime + " after " + config.nudgeMaxRetry + " nudges");
32147
- this.error(_error.message);
32148
- hls.trigger(Events.ERROR, {
32149
- type: ErrorTypes.MEDIA_ERROR,
32150
- details: ErrorDetails.BUFFER_STALLED_ERROR,
32151
- error: _error,
32152
- fatal: true,
32153
- buffer: bufferInfo.len,
32154
- bufferInfo: bufferInfo
32155
- });
32156
- }
32157
- };
32158
- return GapController;
32159
- }(Logger);
32160
-
32161
32352
  function getSourceBuffer() {
32162
32353
  return self.SourceBuffer || self.WebKitSourceBuffer;
32163
32354
  }
@@ -32195,7 +32386,6 @@
32195
32386
  var _this;
32196
32387
  _this = _BaseStreamController.call(this, hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN) || this;
32197
32388
  _this.audioCodecSwap = false;
32198
- _this.gapController = null;
32199
32389
  _this.level = -1;
32200
32390
  _this._forceStartLoad = false;
32201
32391
  _this._hasEnoughToStart = false;
@@ -32207,19 +32397,8 @@
32207
32397
  _this.backtrackFragment = null;
32208
32398
  _this.audioCodecSwitch = false;
32209
32399
  _this.videoBuffer = null;
32210
- _this.onMediaWaiting = function () {
32211
- var gapController = _this.gapController;
32212
- if (gapController) {
32213
- gapController.waiting = self.performance.now();
32214
- }
32215
- };
32216
32400
  _this.onMediaPlaying = function () {
32217
32401
  // tick to speed up FRAG_CHANGED triggering
32218
- var gapController = _this.gapController;
32219
- if (gapController) {
32220
- gapController.ended = 0;
32221
- gapController.waiting = 0;
32222
- }
32223
32402
  _this.tick();
32224
32403
  };
32225
32404
  _this.onMediaSeeked = function () {
@@ -32273,7 +32452,7 @@
32273
32452
  };
32274
32453
  _proto.onHandlerDestroying = function onHandlerDestroying() {
32275
32454
  // @ts-ignore
32276
- this.onMediaPlaying = this.onMediaSeeked = this.onMediaWaiting = null;
32455
+ this.onMediaPlaying = this.onMediaSeeked = null;
32277
32456
  this.unregisterListeners();
32278
32457
  _BaseStreamController.prototype.onHandlerDestroying.call(this);
32279
32458
  };
@@ -32362,8 +32541,11 @@
32362
32541
  this.onTickEnd();
32363
32542
  };
32364
32543
  _proto.onTickEnd = function onTickEnd() {
32544
+ var _this$media2;
32365
32545
  _BaseStreamController.prototype.onTickEnd.call(this);
32366
- this.checkBuffer();
32546
+ if ((_this$media2 = this.media) != null && _this$media2.readyState) {
32547
+ this.lastCurrentTime = this.media.currentTime;
32548
+ }
32367
32549
  this.checkFragmentChanged();
32368
32550
  };
32369
32551
  _proto.doTickIdle = function doTickIdle() {
@@ -32592,27 +32774,17 @@
32592
32774
  _proto.onMediaAttached = function onMediaAttached(event, data) {
32593
32775
  _BaseStreamController.prototype.onMediaAttached.call(this, event, data);
32594
32776
  var media = data.media;
32595
- media.removeEventListener('playing', this.onMediaPlaying);
32596
- media.removeEventListener('seeked', this.onMediaSeeked);
32597
- media.removeEventListener('waiting', this.onMediaWaiting);
32598
- media.addEventListener('playing', this.onMediaPlaying);
32599
- media.addEventListener('seeked', this.onMediaSeeked);
32600
- media.addEventListener('waiting', this.onMediaWaiting);
32601
- this.gapController = new GapController(media, this.fragmentTracker, this.hls);
32777
+ addEventListener(media, 'playing', this.onMediaPlaying);
32778
+ addEventListener(media, 'seeked', this.onMediaSeeked);
32602
32779
  };
32603
32780
  _proto.onMediaDetaching = function onMediaDetaching(event, data) {
32604
32781
  var media = this.media;
32605
32782
  if (media) {
32606
- media.removeEventListener('playing', this.onMediaPlaying);
32607
- media.removeEventListener('seeked', this.onMediaSeeked);
32608
- media.removeEventListener('waiting', this.onMediaWaiting);
32783
+ removeEventListener(media, 'playing', this.onMediaPlaying);
32784
+ removeEventListener(media, 'seeked', this.onMediaSeeked);
32609
32785
  }
32610
32786
  this.videoBuffer = null;
32611
32787
  this.fragPlaying = null;
32612
- if (this.gapController) {
32613
- this.gapController.destroy();
32614
- this.gapController = null;
32615
- }
32616
32788
  _BaseStreamController.prototype.onMediaDetaching.call(this, event, data);
32617
32789
  var transferringMedia = !!data.transferMedia;
32618
32790
  if (transferringMedia) {
@@ -32620,19 +32792,6 @@
32620
32792
  }
32621
32793
  this._hasEnoughToStart = false;
32622
32794
  };
32623
- _proto.triggerEnded = function triggerEnded() {
32624
- var gapController = this.gapController;
32625
- if (gapController) {
32626
- var _this$media2;
32627
- if (gapController.ended) {
32628
- return;
32629
- }
32630
- gapController.ended = ((_this$media2 = this.media) == null ? undefined : _this$media2.currentTime) || 1;
32631
- }
32632
- this.hls.trigger(Events.MEDIA_ENDED, {
32633
- stalled: false
32634
- });
32635
- };
32636
32795
  _proto.onManifestLoading = function onManifestLoading() {
32637
32796
  _BaseStreamController.prototype.onManifestLoading.call(this);
32638
32797
  // reset buffer on manifest loading
@@ -32952,25 +33111,6 @@
32952
33111
  this.recoverWorkerError(data);
32953
33112
  break;
32954
33113
  }
32955
- }
32956
-
32957
- // Checks the health of the buffer and attempts to resolve playback stalls.
32958
- ;
32959
- _proto.checkBuffer = function checkBuffer() {
32960
- var media = this.media,
32961
- gapController = this.gapController;
32962
- if (!media || !gapController || !media.readyState) {
32963
- // Exit early if we don't have media or if the media hasn't buffered anything yet (readyState 0)
32964
- return;
32965
- }
32966
- if (this._hasEnoughToStart || !BufferHelper.getBuffered(media).length) {
32967
- // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
32968
- var state = this.state;
32969
- var activeFrag = state !== State.IDLE ? this.fragCurrent : null;
32970
- var levelDetails = this.getLevelDetails();
32971
- gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
32972
- }
32973
- this.lastCurrentTime = media.currentTime;
32974
33114
  };
32975
33115
  _proto.onFragLoadEmergencyAborted = function onFragLoadEmergencyAborted() {
32976
33116
  this.state = State.IDLE;
@@ -32986,8 +33126,10 @@
32986
33126
  var type = _ref.type;
32987
33127
  if (type !== ElementaryStreamTypes.AUDIO || !this.altAudio) {
32988
33128
  var mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media;
32989
- this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN);
32990
- this.tick();
33129
+ if (mediaBuffer) {
33130
+ this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN);
33131
+ this.tick();
33132
+ }
32991
33133
  }
32992
33134
  };
32993
33135
  _proto.onLevelsUpdated = function onLevelsUpdated(event, data) {
@@ -34306,9 +34448,12 @@
34306
34448
  this.latencyController = undefined;
34307
34449
  this.levelController = undefined;
34308
34450
  this.streamController = undefined;
34451
+ this.audioStreamController = undefined;
34452
+ this.subtititleStreamController = undefined;
34309
34453
  this.audioTrackController = undefined;
34310
34454
  this.subtitleTrackController = undefined;
34311
34455
  this.interstitialsController = undefined;
34456
+ this.gapController = undefined;
34312
34457
  this.emeController = undefined;
34313
34458
  this.cmcdController = undefined;
34314
34459
  this._media = null;
@@ -34346,6 +34491,7 @@
34346
34491
  var id3TrackController = new ID3TrackController(this);
34347
34492
  var keyLoader = new KeyLoader(this.config);
34348
34493
  var streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader);
34494
+ var gapController = this.gapController = new GapController(this, fragmentTracker);
34349
34495
 
34350
34496
  // Cap level controller uses streamController to flush the buffer
34351
34497
  capLevelController.setStreamController(streamController);
@@ -34359,17 +34505,17 @@
34359
34505
  networkControllers.splice(1, 0, contentSteering);
34360
34506
  }
34361
34507
  this.networkControllers = networkControllers;
34362
- var coreComponents = [abrController, bufferController, capLevelController, fpsController, id3TrackController, fragmentTracker];
34508
+ var coreComponents = [abrController, bufferController, gapController, capLevelController, fpsController, id3TrackController, fragmentTracker];
34363
34509
  this.audioTrackController = this.createController(config.audioTrackController, networkControllers);
34364
34510
  var AudioStreamControllerClass = config.audioStreamController;
34365
34511
  if (AudioStreamControllerClass) {
34366
- networkControllers.push(new AudioStreamControllerClass(this, fragmentTracker, keyLoader));
34512
+ networkControllers.push(this.audioStreamController = new AudioStreamControllerClass(this, fragmentTracker, keyLoader));
34367
34513
  }
34368
34514
  // Instantiate subtitleTrackController before SubtitleStreamController to receive level events first
34369
34515
  this.subtitleTrackController = this.createController(config.subtitleTrackController, networkControllers);
34370
34516
  var SubtitleStreamControllerClass = config.subtitleStreamController;
34371
34517
  if (SubtitleStreamControllerClass) {
34372
- networkControllers.push(new SubtitleStreamControllerClass(this, fragmentTracker, keyLoader));
34518
+ networkControllers.push(this.subtititleStreamController = new SubtitleStreamControllerClass(this, fragmentTracker, keyLoader));
34373
34519
  }
34374
34520
  this.createController(config.timelineController, coreComponents);
34375
34521
  keyLoader.emeController = this.emeController = this.createController(config.emeController, coreComponents);
@@ -34644,11 +34790,10 @@
34644
34790
  }
34645
34791
  });
34646
34792
  }
34647
- }
34648
-
34793
+ };
34649
34794
  /**
34650
34795
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
34651
- */;
34796
+ */
34652
34797
  _proto.swapAudioCodec = function swapAudioCodec() {
34653
34798
  this.logger.log('swapAudioCodec');
34654
34799
  this.streamController.swapAudioCodec();
@@ -34746,6 +34891,19 @@
34746
34891
  get: function get() {
34747
34892
  return this.streamController.bufferingEnabled;
34748
34893
  }
34894
+ }, {
34895
+ key: "inFlightFragments",
34896
+ get: function get() {
34897
+ var _inFlightData;
34898
+ var inFlightData = (_inFlightData = {}, _inFlightData[PlaylistLevelType.MAIN] = this.streamController.inFlightFrag, _inFlightData);
34899
+ if (this.audioStreamController) {
34900
+ inFlightData[PlaylistLevelType.AUDIO] = this.audioStreamController.inFlightFrag;
34901
+ }
34902
+ if (this.subtititleStreamController) {
34903
+ inFlightData[PlaylistLevelType.SUBTITLE] = this.subtititleStreamController.inFlightFrag;
34904
+ }
34905
+ return inFlightData;
34906
+ }
34749
34907
  }, {
34750
34908
  key: "sessionId",
34751
34909
  get: function get() {