hls.js 1.6.0-beta.2.0.canary.10923 → 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.10923");
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.10923";
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
  }
@@ -21919,8 +21941,10 @@
21919
21941
  _inheritsLoose(EMEController, _Logger);
21920
21942
  var _proto = EMEController.prototype;
21921
21943
  _proto.destroy = function destroy() {
21944
+ var media = this.media;
21922
21945
  this.unregisterListeners();
21923
21946
  this.onMediaDetached();
21947
+ this._clear(media);
21924
21948
  // Remove any references that could be held in config options or callbacks
21925
21949
  var config = this.config;
21926
21950
  config.requestMediaKeySystemAccessFunc = null;
@@ -22592,15 +22616,17 @@
22592
22616
  media.addEventListener('waitingforkey', this.onWaitingForKey);
22593
22617
  };
22594
22618
  _proto.onMediaDetached = function onMediaDetached() {
22595
- var _this14 = this,
22596
- _media$setMediaKeys;
22597
22619
  var media = this.media;
22598
- var mediaKeysList = this.mediaKeySessions;
22599
22620
  if (media) {
22600
22621
  media.removeEventListener('encrypted', this.onMediaEncrypted);
22601
22622
  media.removeEventListener('waitingforkey', this.onWaitingForKey);
22602
22623
  this.media = null;
22603
22624
  }
22625
+ };
22626
+ _proto._clear = function _clear(media) {
22627
+ var _this14 = this,
22628
+ _media$setMediaKeys;
22629
+ var mediaKeysList = this.mediaKeySessions;
22604
22630
  this._requestLicenseFailureCount = 0;
22605
22631
  this.setMediaKeysQueue = [];
22606
22632
  this.mediaKeySessions = [];
@@ -23835,6 +23861,14 @@
23835
23861
  return AssetListLoader;
23836
23862
  }();
23837
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
+
23838
23872
  function playWithCatch(media) {
23839
23873
  media == null ? undefined : media.play().catch(function () {
23840
23874
  /* no-op */
@@ -24150,24 +24184,23 @@
24150
24184
  this.onScheduleUpdate = null;
24151
24185
  };
24152
24186
  _proto.onDestroying = function onDestroying() {
24153
- var media = this.primaryMedia;
24187
+ var media = this.primaryMedia || this.media;
24154
24188
  if (media) {
24155
24189
  this.removeMediaListeners(media);
24156
24190
  }
24157
24191
  };
24158
24192
  _proto.removeMediaListeners = function removeMediaListeners(media) {
24159
- media.removeEventListener('play', this.onPlay);
24160
- media.removeEventListener('pause', this.onPause);
24161
- media.removeEventListener('seeking', this.onSeeking);
24162
- 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);
24163
24197
  };
24164
24198
  _proto.onMediaAttaching = function onMediaAttaching(event, data) {
24165
24199
  var media = this.media = data.media;
24166
- this.removeMediaListeners(media);
24167
- media.addEventListener('seeking', this.onSeeking);
24168
- media.addEventListener('timeupdate', this.onTimeupdate);
24169
- media.addEventListener('play', this.onPlay);
24170
- 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);
24171
24204
  };
24172
24205
  _proto.onMediaAttached = function onMediaAttached(event, data) {
24173
24206
  var playingItem = this.playingItem;
@@ -25672,7 +25705,7 @@
25672
25705
  }]);
25673
25706
  }(Logger);
25674
25707
 
25675
- var TICK_INTERVAL$1 = 500; // how often to tick in ms
25708
+ var TICK_INTERVAL$2 = 500; // how often to tick in ms
25676
25709
 
25677
25710
  var SubtitleStreamController = /*#__PURE__*/function (_BaseStreamController) {
25678
25711
  function SubtitleStreamController(hls, fragmentTracker, keyLoader) {
@@ -25714,7 +25747,7 @@
25714
25747
  _proto.startLoad = function startLoad(startPosition) {
25715
25748
  this.stopLoad();
25716
25749
  this.state = State.IDLE;
25717
- this.setInterval(TICK_INTERVAL$1);
25750
+ this.setInterval(TICK_INTERVAL$2);
25718
25751
  this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition;
25719
25752
  this.tick();
25720
25753
  };
@@ -25850,7 +25883,7 @@
25850
25883
  this.mediaBuffer = null;
25851
25884
  }
25852
25885
  if (currentTrack && this.state !== State.STOPPED) {
25853
- this.setInterval(TICK_INTERVAL$1);
25886
+ this.setInterval(TICK_INTERVAL$2);
25854
25887
  }
25855
25888
  }
25856
25889
 
@@ -30251,16 +30284,20 @@
30251
30284
  frontBufferFlushThreshold: Infinity,
30252
30285
  maxBufferSize: 60 * 1000 * 1000,
30253
30286
  // used by stream-controller
30254
- maxBufferHole: 0.1,
30287
+ maxFragLookUpTolerance: 0.25,
30255
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
30256
30293
  highBufferWatchdogPeriod: 2,
30257
- // used by stream-controller
30294
+ // used by gap-controller
30258
30295
  nudgeOffset: 0.1,
30259
- // used by stream-controller
30296
+ // used by gap-controller
30260
30297
  nudgeMaxRetry: 3,
30261
- // used by stream-controller
30262
- maxFragLookUpTolerance: 0.25,
30263
- // used by stream-controller
30298
+ // used by gap-controller
30299
+ nudgeOnVideoHole: true,
30300
+ // used by gap-controller
30264
30301
  liveSyncDurationCount: 3,
30265
30302
  // used by latency-controller
30266
30303
  liveSyncOnStallIncrease: 1,
@@ -30359,7 +30396,6 @@
30359
30396
  progressive: false,
30360
30397
  lowLatencyMode: true,
30361
30398
  cmcd: undefined,
30362
- detectStallWithCurrentTimeMs: 1250,
30363
30399
  enableDateRangeMetadataCues: true,
30364
30400
  enableEmsgMetadataCues: true,
30365
30401
  enableEmsgKLVMetadata: false,
@@ -30614,6 +30650,534 @@
30614
30650
  }
30615
30651
  }
30616
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
+
30617
31181
  var MIN_CUE_DURATION = 0.25;
30618
31182
  function getCueClass() {
30619
31183
  if (typeof self === 'undefined') return undefined;
@@ -31785,375 +32349,6 @@
31785
32349
  });
31786
32350
  }
31787
32351
 
31788
- var MAX_START_GAP_JUMP = 2.0;
31789
- var SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
31790
- var SKIP_BUFFER_RANGE_START = 0.05;
31791
- var GapController = /*#__PURE__*/function (_Logger) {
31792
- function GapController(media, fragmentTracker, hls) {
31793
- var _this;
31794
- _this = _Logger.call(this, 'gap-controller', hls.logger) || this;
31795
- _this.media = null;
31796
- _this.fragmentTracker = null;
31797
- _this.hls = null;
31798
- _this.nudgeRetry = 0;
31799
- _this.stallReported = false;
31800
- _this.stalled = null;
31801
- _this.moved = false;
31802
- _this.seeking = false;
31803
- _this.ended = 0;
31804
- _this.waiting = 0;
31805
- _this.media = media;
31806
- _this.fragmentTracker = fragmentTracker;
31807
- _this.hls = hls;
31808
- return _this;
31809
- }
31810
- _inheritsLoose(GapController, _Logger);
31811
- var _proto = GapController.prototype;
31812
- _proto.destroy = function destroy() {
31813
- this.media = this.hls = this.fragmentTracker = null;
31814
- }
31815
-
31816
- /**
31817
- * Checks if the playhead is stuck within a gap, and if so, attempts to free it.
31818
- * A gap is an unbuffered range between two buffered ranges (or the start and the first buffered range).
31819
- *
31820
- * @param lastCurrentTime - Previously read playhead position
31821
- */;
31822
- _proto.poll = function poll(lastCurrentTime, activeFrag, levelDetails, state) {
31823
- var _this$hls;
31824
- var media = this.media,
31825
- stalled = this.stalled;
31826
- if (!media) {
31827
- return;
31828
- }
31829
- var currentTime = media.currentTime,
31830
- seeking = media.seeking;
31831
- var seeked = this.seeking && !seeking;
31832
- var beginSeek = !this.seeking && seeking;
31833
- this.seeking = seeking;
31834
-
31835
- // The playhead is moving, no-op
31836
- if (currentTime !== lastCurrentTime) {
31837
- if (lastCurrentTime) {
31838
- this.ended = 0;
31839
- }
31840
- this.moved = true;
31841
- if (!seeking) {
31842
- this.nudgeRetry = 0;
31843
- }
31844
- if (this.waiting === 0) {
31845
- this.stallResolved(currentTime);
31846
- }
31847
- return;
31848
- }
31849
-
31850
- // Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek
31851
- if (beginSeek || seeked) {
31852
- if (seeked) {
31853
- this.stallResolved(currentTime);
31854
- }
31855
- return;
31856
- }
31857
-
31858
- // The playhead should not be moving
31859
- if (media.paused && !seeking || media.ended || media.playbackRate === 0) {
31860
- this.nudgeRetry = 0;
31861
- this.stallResolved(currentTime);
31862
- // Fire MEDIA_ENDED to workaround event not being dispatched by browser
31863
- if (!this.ended && media.ended && this.hls) {
31864
- this.ended = currentTime || 1;
31865
- this.hls.trigger(Events.MEDIA_ENDED, {
31866
- stalled: false
31867
- });
31868
- }
31869
- return;
31870
- }
31871
- if (!BufferHelper.getBuffered(media).length) {
31872
- this.nudgeRetry = 0;
31873
- return;
31874
- }
31875
- var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
31876
- var nextStart = bufferInfo.nextStart || 0;
31877
- var fragmentTracker = this.fragmentTracker;
31878
- if (seeking && fragmentTracker) {
31879
- // Waiting for seeking in a buffered range to complete
31880
- var hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
31881
- // Next buffered range is too far ahead to jump to while still seeking
31882
- var noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !fragmentTracker.getPartialFragment(currentTime);
31883
- if (hasEnoughBuffer || noBufferGap) {
31884
- return;
31885
- }
31886
- // Reset moved state when seeking to a point in or before a gap
31887
- this.moved = false;
31888
- }
31889
-
31890
- // Skip start gaps if we haven't played, but the last poll detected the start of a stall
31891
- // The addition poll gives the browser a chance to jump the gap for us
31892
- if (!this.moved && this.stalled !== null && fragmentTracker) {
31893
- // There is no playable buffer (seeked, waiting for buffer)
31894
- var isBuffered = bufferInfo.len > 0;
31895
- if (!isBuffered && !nextStart) {
31896
- return;
31897
- }
31898
- // Jump start gaps within jump threshold
31899
- var startJump = Math.max(nextStart, bufferInfo.start || 0) - currentTime;
31900
-
31901
- // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
31902
- // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
31903
- // that begins over 1 target duration after the video start position.
31904
- var isLive = !!(levelDetails != null && levelDetails.live);
31905
- var maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
31906
- var partialOrGap = fragmentTracker.getPartialFragment(currentTime);
31907
- if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
31908
- if (!media.paused) {
31909
- this._trySkipBufferHole(partialOrGap);
31910
- }
31911
- return;
31912
- }
31913
- }
31914
-
31915
- // Start tracking stall time
31916
- var config = (_this$hls = this.hls) == null ? undefined : _this$hls.config;
31917
- if (!config) {
31918
- return;
31919
- }
31920
- var detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs;
31921
- var tnow = self.performance.now();
31922
- var tWaiting = this.waiting;
31923
- if (stalled === null) {
31924
- // Use time of recent "waiting" event
31925
- if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) {
31926
- this.stalled = tWaiting;
31927
- } else {
31928
- this.stalled = tnow;
31929
- }
31930
- return;
31931
- }
31932
- var stalledDuration = tnow - stalled;
31933
- if (!seeking && (stalledDuration >= detectStallWithCurrentTimeMs || tWaiting) && this.hls) {
31934
- // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
31935
- if (state === State.ENDED && !(levelDetails != null && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? undefined : levelDetails.edge) || 0)) < 1) {
31936
- if (this.ended) {
31937
- return;
31938
- }
31939
- this.ended = currentTime || 1;
31940
- this.hls.trigger(Events.MEDIA_ENDED, {
31941
- stalled: true
31942
- });
31943
- return;
31944
- }
31945
- // Report stalling after trying to fix
31946
- this._reportStall(bufferInfo);
31947
- if (!this.media || !this.hls) {
31948
- return;
31949
- }
31950
- }
31951
- var bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole);
31952
- this._tryFixBufferStall(bufferedWithHoles, stalledDuration);
31953
- };
31954
- _proto.stallResolved = function stallResolved(currentTime) {
31955
- var stalled = this.stalled;
31956
- if (stalled && this.hls) {
31957
- this.stalled = null;
31958
- // The playhead is now moving, but was previously stalled
31959
- if (this.stallReported) {
31960
- var stalledDuration = self.performance.now() - stalled;
31961
- this.warn("playback not stuck anymore @" + currentTime + ", after " + Math.round(stalledDuration) + "ms");
31962
- this.stallReported = false;
31963
- this.waiting = 0;
31964
- this.hls.trigger(Events.STALL_RESOLVED, {});
31965
- }
31966
- }
31967
- }
31968
-
31969
- /**
31970
- * Detects and attempts to fix known buffer stalling issues.
31971
- * @param bufferInfo - The properties of the current buffer.
31972
- * @param stalledDurationMs - The amount of time Hls.js has been stalling for.
31973
- * @private
31974
- */;
31975
- _proto._tryFixBufferStall = function _tryFixBufferStall(bufferInfo, stalledDurationMs) {
31976
- var _this$hls2;
31977
- var fragmentTracker = this.fragmentTracker,
31978
- media = this.media;
31979
- var config = (_this$hls2 = this.hls) == null ? undefined : _this$hls2.config;
31980
- if (!media || !fragmentTracker || !config) {
31981
- return;
31982
- }
31983
- var currentTime = media.currentTime;
31984
- var partial = fragmentTracker.getPartialFragment(currentTime);
31985
- if (partial) {
31986
- // Try to skip over the buffer hole caused by a partial fragment
31987
- // This method isn't limited by the size of the gap between buffered ranges
31988
- var targetTime = this._trySkipBufferHole(partial);
31989
- // we return here in this case, meaning
31990
- // the branch below only executes when we haven't seeked to a new position
31991
- if (targetTime || !this.media) {
31992
- return;
31993
- }
31994
- }
31995
-
31996
- // if we haven't had to skip over a buffer hole of a partial fragment
31997
- // we may just have to "nudge" the playlist as the browser decoding/rendering engine
31998
- // needs to cross some sort of threshold covering all source-buffers content
31999
- // to start playing properly.
32000
- var bufferedRanges = bufferInfo.buffered;
32001
- if ((bufferedRanges && bufferedRanges.length > 1 && bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
32002
- this.warn('Trying to nudge playhead over buffer-hole');
32003
- // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
32004
- // We only try to jump the hole if it's under the configured size
32005
- this._tryNudgeBuffer(bufferInfo);
32006
- }
32007
- }
32008
-
32009
- /**
32010
- * Triggers a BUFFER_STALLED_ERROR event, but only once per stall period.
32011
- * @param bufferLen - The playhead distance from the end of the current buffer segment.
32012
- * @private
32013
- */;
32014
- _proto._reportStall = function _reportStall(bufferInfo) {
32015
- var hls = this.hls,
32016
- media = this.media,
32017
- stallReported = this.stallReported,
32018
- stalled = this.stalled;
32019
- if (!stallReported && stalled !== null && media && hls) {
32020
- // Report stalled error once
32021
- this.stallReported = true;
32022
- var error = new Error("Playback stalling at @" + media.currentTime + " due to low buffer (" + JSON.stringify(bufferInfo) + ")");
32023
- this.warn(error.message);
32024
- hls.trigger(Events.ERROR, {
32025
- type: ErrorTypes.MEDIA_ERROR,
32026
- details: ErrorDetails.BUFFER_STALLED_ERROR,
32027
- fatal: false,
32028
- error: error,
32029
- buffer: bufferInfo.len,
32030
- bufferInfo: bufferInfo,
32031
- stalled: {
32032
- start: stalled
32033
- }
32034
- });
32035
- }
32036
- }
32037
-
32038
- /**
32039
- * Attempts to fix buffer stalls by jumping over known gaps caused by partial fragments
32040
- * @param partial - The partial fragment found at the current time (where playback is stalling).
32041
- * @private
32042
- */;
32043
- _proto._trySkipBufferHole = function _trySkipBufferHole(partial) {
32044
- var _this$hls3;
32045
- var fragmentTracker = this.fragmentTracker,
32046
- media = this.media;
32047
- var config = (_this$hls3 = this.hls) == null ? undefined : _this$hls3.config;
32048
- if (!media || !fragmentTracker || !config) {
32049
- return 0;
32050
- }
32051
-
32052
- // Check if currentTime is between unbuffered regions of partial fragments
32053
- var currentTime = media.currentTime;
32054
- var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
32055
- var startTime = currentTime < bufferInfo.start ? bufferInfo.start : bufferInfo.nextStart;
32056
- if (startTime) {
32057
- var bufferStarved = bufferInfo.len <= config.maxBufferHole;
32058
- var waiting = bufferInfo.len > 0 && bufferInfo.len < 1 && media.readyState < 3;
32059
- var gapLength = startTime - currentTime;
32060
- if (gapLength > 0 && (bufferStarved || waiting)) {
32061
- // Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial
32062
- if (gapLength > config.maxBufferHole) {
32063
- var startGap = false;
32064
- if (currentTime === 0) {
32065
- var startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN);
32066
- if (startFrag && startTime < startFrag.end) {
32067
- startGap = true;
32068
- }
32069
- }
32070
- if (!startGap) {
32071
- var startProvisioned = partial || fragmentTracker.getAppendedFrag(currentTime, PlaylistLevelType.MAIN);
32072
- if (startProvisioned) {
32073
- var moreToLoad = false;
32074
- var pos = startProvisioned.end;
32075
- while (pos < startTime) {
32076
- var provisioned = fragmentTracker.getPartialFragment(pos);
32077
- if (provisioned) {
32078
- pos += provisioned.duration;
32079
- } else {
32080
- moreToLoad = true;
32081
- break;
32082
- }
32083
- }
32084
- if (moreToLoad) {
32085
- return 0;
32086
- }
32087
- }
32088
- }
32089
- }
32090
- var targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
32091
- this.warn("skipping hole, adjusting currentTime from " + currentTime + " to " + targetTime);
32092
- this.moved = true;
32093
- media.currentTime = targetTime;
32094
- if (partial && !partial.gap && this.hls) {
32095
- var error = new Error("fragment loaded with buffer holes, seeking from " + currentTime + " to " + targetTime);
32096
- this.hls.trigger(Events.ERROR, {
32097
- type: ErrorTypes.MEDIA_ERROR,
32098
- details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
32099
- fatal: false,
32100
- error: error,
32101
- reason: error.message,
32102
- frag: partial,
32103
- buffer: bufferInfo.len,
32104
- bufferInfo: bufferInfo
32105
- });
32106
- }
32107
- return targetTime;
32108
- }
32109
- }
32110
- return 0;
32111
- }
32112
-
32113
- /**
32114
- * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount.
32115
- * @private
32116
- */;
32117
- _proto._tryNudgeBuffer = function _tryNudgeBuffer(bufferInfo) {
32118
- var hls = this.hls,
32119
- media = this.media,
32120
- nudgeRetry = this.nudgeRetry;
32121
- var config = hls == null ? undefined : hls.config;
32122
- if (!media || !config) {
32123
- return 0;
32124
- }
32125
- var currentTime = media.currentTime;
32126
- this.nudgeRetry++;
32127
- if (nudgeRetry < config.nudgeMaxRetry) {
32128
- var targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
32129
- // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
32130
- var error = new Error("Nudging 'currentTime' from " + currentTime + " to " + targetTime);
32131
- this.warn(error.message);
32132
- media.currentTime = targetTime;
32133
- hls.trigger(Events.ERROR, {
32134
- type: ErrorTypes.MEDIA_ERROR,
32135
- details: ErrorDetails.BUFFER_NUDGE_ON_STALL,
32136
- error: error,
32137
- fatal: false,
32138
- buffer: bufferInfo.len,
32139
- bufferInfo: bufferInfo
32140
- });
32141
- } else {
32142
- var _error = new Error("Playhead still not moving while enough data buffered @" + currentTime + " after " + config.nudgeMaxRetry + " nudges");
32143
- this.error(_error.message);
32144
- hls.trigger(Events.ERROR, {
32145
- type: ErrorTypes.MEDIA_ERROR,
32146
- details: ErrorDetails.BUFFER_STALLED_ERROR,
32147
- error: _error,
32148
- fatal: true,
32149
- buffer: bufferInfo.len,
32150
- bufferInfo: bufferInfo
32151
- });
32152
- }
32153
- };
32154
- return GapController;
32155
- }(Logger);
32156
-
32157
32352
  function getSourceBuffer() {
32158
32353
  return self.SourceBuffer || self.WebKitSourceBuffer;
32159
32354
  }
@@ -32191,7 +32386,6 @@
32191
32386
  var _this;
32192
32387
  _this = _BaseStreamController.call(this, hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN) || this;
32193
32388
  _this.audioCodecSwap = false;
32194
- _this.gapController = null;
32195
32389
  _this.level = -1;
32196
32390
  _this._forceStartLoad = false;
32197
32391
  _this._hasEnoughToStart = false;
@@ -32203,19 +32397,8 @@
32203
32397
  _this.backtrackFragment = null;
32204
32398
  _this.audioCodecSwitch = false;
32205
32399
  _this.videoBuffer = null;
32206
- _this.onMediaWaiting = function () {
32207
- var gapController = _this.gapController;
32208
- if (gapController) {
32209
- gapController.waiting = self.performance.now();
32210
- }
32211
- };
32212
32400
  _this.onMediaPlaying = function () {
32213
32401
  // tick to speed up FRAG_CHANGED triggering
32214
- var gapController = _this.gapController;
32215
- if (gapController) {
32216
- gapController.ended = 0;
32217
- gapController.waiting = 0;
32218
- }
32219
32402
  _this.tick();
32220
32403
  };
32221
32404
  _this.onMediaSeeked = function () {
@@ -32269,7 +32452,7 @@
32269
32452
  };
32270
32453
  _proto.onHandlerDestroying = function onHandlerDestroying() {
32271
32454
  // @ts-ignore
32272
- this.onMediaPlaying = this.onMediaSeeked = this.onMediaWaiting = null;
32455
+ this.onMediaPlaying = this.onMediaSeeked = null;
32273
32456
  this.unregisterListeners();
32274
32457
  _BaseStreamController.prototype.onHandlerDestroying.call(this);
32275
32458
  };
@@ -32358,8 +32541,11 @@
32358
32541
  this.onTickEnd();
32359
32542
  };
32360
32543
  _proto.onTickEnd = function onTickEnd() {
32544
+ var _this$media2;
32361
32545
  _BaseStreamController.prototype.onTickEnd.call(this);
32362
- this.checkBuffer();
32546
+ if ((_this$media2 = this.media) != null && _this$media2.readyState) {
32547
+ this.lastCurrentTime = this.media.currentTime;
32548
+ }
32363
32549
  this.checkFragmentChanged();
32364
32550
  };
32365
32551
  _proto.doTickIdle = function doTickIdle() {
@@ -32588,27 +32774,17 @@
32588
32774
  _proto.onMediaAttached = function onMediaAttached(event, data) {
32589
32775
  _BaseStreamController.prototype.onMediaAttached.call(this, event, data);
32590
32776
  var media = data.media;
32591
- media.removeEventListener('playing', this.onMediaPlaying);
32592
- media.removeEventListener('seeked', this.onMediaSeeked);
32593
- media.removeEventListener('waiting', this.onMediaWaiting);
32594
- media.addEventListener('playing', this.onMediaPlaying);
32595
- media.addEventListener('seeked', this.onMediaSeeked);
32596
- media.addEventListener('waiting', this.onMediaWaiting);
32597
- this.gapController = new GapController(media, this.fragmentTracker, this.hls);
32777
+ addEventListener(media, 'playing', this.onMediaPlaying);
32778
+ addEventListener(media, 'seeked', this.onMediaSeeked);
32598
32779
  };
32599
32780
  _proto.onMediaDetaching = function onMediaDetaching(event, data) {
32600
32781
  var media = this.media;
32601
32782
  if (media) {
32602
- media.removeEventListener('playing', this.onMediaPlaying);
32603
- media.removeEventListener('seeked', this.onMediaSeeked);
32604
- media.removeEventListener('waiting', this.onMediaWaiting);
32783
+ removeEventListener(media, 'playing', this.onMediaPlaying);
32784
+ removeEventListener(media, 'seeked', this.onMediaSeeked);
32605
32785
  }
32606
32786
  this.videoBuffer = null;
32607
32787
  this.fragPlaying = null;
32608
- if (this.gapController) {
32609
- this.gapController.destroy();
32610
- this.gapController = null;
32611
- }
32612
32788
  _BaseStreamController.prototype.onMediaDetaching.call(this, event, data);
32613
32789
  var transferringMedia = !!data.transferMedia;
32614
32790
  if (transferringMedia) {
@@ -32616,19 +32792,6 @@
32616
32792
  }
32617
32793
  this._hasEnoughToStart = false;
32618
32794
  };
32619
- _proto.triggerEnded = function triggerEnded() {
32620
- var gapController = this.gapController;
32621
- if (gapController) {
32622
- var _this$media2;
32623
- if (gapController.ended) {
32624
- return;
32625
- }
32626
- gapController.ended = ((_this$media2 = this.media) == null ? undefined : _this$media2.currentTime) || 1;
32627
- }
32628
- this.hls.trigger(Events.MEDIA_ENDED, {
32629
- stalled: false
32630
- });
32631
- };
32632
32795
  _proto.onManifestLoading = function onManifestLoading() {
32633
32796
  _BaseStreamController.prototype.onManifestLoading.call(this);
32634
32797
  // reset buffer on manifest loading
@@ -32948,25 +33111,6 @@
32948
33111
  this.recoverWorkerError(data);
32949
33112
  break;
32950
33113
  }
32951
- }
32952
-
32953
- // Checks the health of the buffer and attempts to resolve playback stalls.
32954
- ;
32955
- _proto.checkBuffer = function checkBuffer() {
32956
- var media = this.media,
32957
- gapController = this.gapController;
32958
- if (!media || !gapController || !media.readyState) {
32959
- // Exit early if we don't have media or if the media hasn't buffered anything yet (readyState 0)
32960
- return;
32961
- }
32962
- if (this._hasEnoughToStart || !BufferHelper.getBuffered(media).length) {
32963
- // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
32964
- var state = this.state;
32965
- var activeFrag = state !== State.IDLE ? this.fragCurrent : null;
32966
- var levelDetails = this.getLevelDetails();
32967
- gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
32968
- }
32969
- this.lastCurrentTime = media.currentTime;
32970
33114
  };
32971
33115
  _proto.onFragLoadEmergencyAborted = function onFragLoadEmergencyAborted() {
32972
33116
  this.state = State.IDLE;
@@ -32982,8 +33126,10 @@
32982
33126
  var type = _ref.type;
32983
33127
  if (type !== ElementaryStreamTypes.AUDIO || !this.altAudio) {
32984
33128
  var mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media;
32985
- this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN);
32986
- this.tick();
33129
+ if (mediaBuffer) {
33130
+ this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN);
33131
+ this.tick();
33132
+ }
32987
33133
  }
32988
33134
  };
32989
33135
  _proto.onLevelsUpdated = function onLevelsUpdated(event, data) {
@@ -34302,9 +34448,12 @@
34302
34448
  this.latencyController = undefined;
34303
34449
  this.levelController = undefined;
34304
34450
  this.streamController = undefined;
34451
+ this.audioStreamController = undefined;
34452
+ this.subtititleStreamController = undefined;
34305
34453
  this.audioTrackController = undefined;
34306
34454
  this.subtitleTrackController = undefined;
34307
34455
  this.interstitialsController = undefined;
34456
+ this.gapController = undefined;
34308
34457
  this.emeController = undefined;
34309
34458
  this.cmcdController = undefined;
34310
34459
  this._media = null;
@@ -34342,6 +34491,7 @@
34342
34491
  var id3TrackController = new ID3TrackController(this);
34343
34492
  var keyLoader = new KeyLoader(this.config);
34344
34493
  var streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader);
34494
+ var gapController = this.gapController = new GapController(this, fragmentTracker);
34345
34495
 
34346
34496
  // Cap level controller uses streamController to flush the buffer
34347
34497
  capLevelController.setStreamController(streamController);
@@ -34355,17 +34505,17 @@
34355
34505
  networkControllers.splice(1, 0, contentSteering);
34356
34506
  }
34357
34507
  this.networkControllers = networkControllers;
34358
- var coreComponents = [abrController, bufferController, capLevelController, fpsController, id3TrackController, fragmentTracker];
34508
+ var coreComponents = [abrController, bufferController, gapController, capLevelController, fpsController, id3TrackController, fragmentTracker];
34359
34509
  this.audioTrackController = this.createController(config.audioTrackController, networkControllers);
34360
34510
  var AudioStreamControllerClass = config.audioStreamController;
34361
34511
  if (AudioStreamControllerClass) {
34362
- networkControllers.push(new AudioStreamControllerClass(this, fragmentTracker, keyLoader));
34512
+ networkControllers.push(this.audioStreamController = new AudioStreamControllerClass(this, fragmentTracker, keyLoader));
34363
34513
  }
34364
34514
  // Instantiate subtitleTrackController before SubtitleStreamController to receive level events first
34365
34515
  this.subtitleTrackController = this.createController(config.subtitleTrackController, networkControllers);
34366
34516
  var SubtitleStreamControllerClass = config.subtitleStreamController;
34367
34517
  if (SubtitleStreamControllerClass) {
34368
- networkControllers.push(new SubtitleStreamControllerClass(this, fragmentTracker, keyLoader));
34518
+ networkControllers.push(this.subtititleStreamController = new SubtitleStreamControllerClass(this, fragmentTracker, keyLoader));
34369
34519
  }
34370
34520
  this.createController(config.timelineController, coreComponents);
34371
34521
  keyLoader.emeController = this.emeController = this.createController(config.emeController, coreComponents);
@@ -34640,11 +34790,10 @@
34640
34790
  }
34641
34791
  });
34642
34792
  }
34643
- }
34644
-
34793
+ };
34645
34794
  /**
34646
34795
  * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
34647
- */;
34796
+ */
34648
34797
  _proto.swapAudioCodec = function swapAudioCodec() {
34649
34798
  this.logger.log('swapAudioCodec');
34650
34799
  this.streamController.swapAudioCodec();
@@ -34742,6 +34891,19 @@
34742
34891
  get: function get() {
34743
34892
  return this.streamController.bufferingEnabled;
34744
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
+ }
34745
34907
  }, {
34746
34908
  key: "sessionId",
34747
34909
  get: function get() {