hls.js 1.6.0-beta.2.0.canary.10924 → 1.6.0-beta.2.0.canary.10926
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.d.mts +68 -30
- package/dist/hls.d.ts +68 -30
- package/dist/hls.js +684 -496
- package/dist/hls.js.d.ts +68 -30
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +3882 -3693
- package/dist/hls.light.js.map +1 -1
- package/dist/hls.light.min.js +1 -1
- package/dist/hls.light.min.js.map +1 -1
- package/dist/hls.light.mjs +1140 -954
- package/dist/hls.light.mjs.map +1 -1
- package/dist/hls.min.js +1 -1
- package/dist/hls.min.js.map +1 -1
- package/dist/hls.mjs +684 -499
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/package.json +1 -1
- package/src/config.ts +15 -9
- package/src/controller/abr-controller.ts +2 -2
- package/src/controller/base-stream-controller.ts +16 -12
- package/src/controller/buffer-controller.ts +19 -22
- package/src/controller/error-controller.ts +2 -2
- package/src/controller/fragment-tracker.ts +1 -1
- package/src/controller/gap-controller.ts +273 -38
- package/src/controller/interstitials-controller.ts +14 -11
- package/src/controller/level-controller.ts +4 -0
- package/src/controller/stream-controller.ts +26 -73
- package/src/hls.ts +57 -3
- package/src/utils/buffer-helper.ts +35 -13
- package/src/utils/event-listener-helper.ts +16 -0
- package/src/utils/rendition-helper.ts +1 -1
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.
|
1062
|
+
newLogger.log("Debug logs enabled for \"" + context + "\" in hls.js version " + "1.6.0-beta.2.0.canary.10926");
|
1063
1063
|
} catch (e) {
|
1064
1064
|
/* log fn threw an exception. All logger methods are no-ops. */
|
1065
1065
|
return createLogger();
|
@@ -2049,8 +2049,8 @@
|
|
2049
2049
|
return -1;
|
2050
2050
|
}
|
2051
2051
|
function useAlternateAudio(audioTrackUrl, hls) {
|
2052
|
-
var _hls$
|
2053
|
-
return !!audioTrackUrl && audioTrackUrl !== ((_hls$
|
2052
|
+
var _hls$loadLevelObj;
|
2053
|
+
return !!audioTrackUrl && audioTrackUrl !== ((_hls$loadLevelObj = hls.loadLevelObj) == null ? undefined : _hls$loadLevelObj.uri);
|
2054
2054
|
}
|
2055
2055
|
|
2056
2056
|
var AbrController = /*#__PURE__*/function (_Logger) {
|
@@ -2470,8 +2470,8 @@
|
|
2470
2470
|
}
|
2471
2471
|
// If no matching level found, see if min auto level would be a better option
|
2472
2472
|
var minLevel = hls.levels[minAutoLevel];
|
2473
|
-
var autoLevel = hls.
|
2474
|
-
if ((minLevel == null ? undefined : minLevel.bitrate) <
|
2473
|
+
var autoLevel = hls.loadLevelObj;
|
2474
|
+
if (autoLevel && (minLevel == null ? undefined : minLevel.bitrate) < autoLevel.bitrate) {
|
2475
2475
|
return minAutoLevel;
|
2476
2476
|
}
|
2477
2477
|
// or if bitrate is not lower, continue to use loadLevel
|
@@ -3086,7 +3086,7 @@
|
|
3086
3086
|
case ErrorDetails.SUBTITLE_LOAD_ERROR:
|
3087
3087
|
case ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT:
|
3088
3088
|
if (context) {
|
3089
|
-
var level = hls.
|
3089
|
+
var level = hls.loadLevelObj;
|
3090
3090
|
if (level && (context.type === PlaylistContextType.AUDIO_TRACK && level.hasAudioGroup(context.groupId) || context.type === PlaylistContextType.SUBTITLE_TRACK && level.hasSubtitleGroup(context.groupId))) {
|
3091
3091
|
// Perform Pathway switch or Redundant failover if possible for fastest recovery
|
3092
3092
|
// otherwise allow playlist retry count to reach max error retries
|
@@ -3099,7 +3099,7 @@
|
|
3099
3099
|
return;
|
3100
3100
|
case ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED:
|
3101
3101
|
{
|
3102
|
-
var _level = hls.
|
3102
|
+
var _level = hls.loadLevelObj;
|
3103
3103
|
var restrictedHdcpLevel = _level == null ? undefined : _level.attrs['HDCP-LEVEL'];
|
3104
3104
|
if (restrictedHdcpLevel) {
|
3105
3105
|
data.errorAction = {
|
@@ -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
|
6510
|
-
if (
|
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.
|
6531
|
-
|
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
|
-
//
|
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
|
-
|
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:
|
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
|
};
|
@@ -9614,7 +9629,8 @@
|
|
9614
9629
|
if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
|
9615
9630
|
var bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
|
9616
9631
|
if (bufferedFragAtPos && (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)) {
|
9617
|
-
|
9632
|
+
var gapDuration = Math.max(Math.min(bufferInfo.nextStart, bufferedFragAtPos.end) - pos, maxBufferHole);
|
9633
|
+
return BufferHelper.bufferInfo(bufferable, pos, gapDuration);
|
9618
9634
|
}
|
9619
9635
|
}
|
9620
9636
|
return bufferInfo;
|
@@ -10194,6 +10210,14 @@
|
|
10194
10210
|
get: function get() {
|
10195
10211
|
return this.buffering;
|
10196
10212
|
}
|
10213
|
+
}, {
|
10214
|
+
key: "inFlightFrag",
|
10215
|
+
get: function get() {
|
10216
|
+
return {
|
10217
|
+
frag: this.fragCurrent,
|
10218
|
+
state: this.state
|
10219
|
+
};
|
10220
|
+
}
|
10197
10221
|
}, {
|
10198
10222
|
key: "state",
|
10199
10223
|
get: function get() {
|
@@ -16311,7 +16335,7 @@
|
|
16311
16335
|
return !remuxResult.audio && !remuxResult.video && !remuxResult.text && !remuxResult.id3 && !remuxResult.initSegment;
|
16312
16336
|
}
|
16313
16337
|
|
16314
|
-
var version = "1.6.0-beta.2.0.canary.
|
16338
|
+
var version = "1.6.0-beta.2.0.canary.10926";
|
16315
16339
|
|
16316
16340
|
// ensure the worker ends up in the bundle
|
16317
16341
|
// If the worker should not be included this gets aliased to empty.js
|
@@ -16682,7 +16706,7 @@
|
|
16682
16706
|
return TransmuxerInterface;
|
16683
16707
|
}();
|
16684
16708
|
|
16685
|
-
var TICK_INTERVAL$
|
16709
|
+
var TICK_INTERVAL$3 = 100; // how often to tick in ms
|
16686
16710
|
var AudioStreamController = /*#__PURE__*/function (_BaseStreamController) {
|
16687
16711
|
function AudioStreamController(hls, fragmentTracker, keyLoader) {
|
16688
16712
|
var _this;
|
@@ -16793,7 +16817,7 @@
|
|
16793
16817
|
}
|
16794
16818
|
var lastCurrentTime = this.lastCurrentTime;
|
16795
16819
|
this.stopLoad();
|
16796
|
-
this.setInterval(TICK_INTERVAL$
|
16820
|
+
this.setInterval(TICK_INTERVAL$3);
|
16797
16821
|
if (lastCurrentTime > 0 && startPosition === -1) {
|
16798
16822
|
this.log("Override startPosition with lastCurrentTime @" + lastCurrentTime.toFixed(3));
|
16799
16823
|
startPosition = lastCurrentTime;
|
@@ -17025,7 +17049,7 @@
|
|
17025
17049
|
this.flushAudioIfNeeded(data);
|
17026
17050
|
if (this.state !== State.STOPPED) {
|
17027
17051
|
// switching to audio track, start timer if not already started
|
17028
|
-
this.setInterval(TICK_INTERVAL$
|
17052
|
+
this.setInterval(TICK_INTERVAL$3);
|
17029
17053
|
this.state = State.IDLE;
|
17030
17054
|
this.tick();
|
17031
17055
|
}
|
@@ -18732,7 +18756,6 @@
|
|
18732
18756
|
var sbTrack = transferredTrack != null && transferredTrack.buffer ? transferredTrack : track;
|
18733
18757
|
var sbCodec = (sbTrack == null ? undefined : sbTrack.pendingCodec) || (sbTrack == null ? undefined : sbTrack.codec);
|
18734
18758
|
var trackLevelCodec = sbTrack == null ? undefined : sbTrack.levelCodec;
|
18735
|
-
var forceChangeType = !sbTrack || !!_this9.hls.config.assetPlayerId;
|
18736
18759
|
if (!track) {
|
18737
18760
|
track = tracks[trackName] = {
|
18738
18761
|
buffer: undefined,
|
@@ -18749,7 +18772,7 @@
|
|
18749
18772
|
var currentCodec = currentCodecFull == null ? undefined : currentCodecFull.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');
|
18750
18773
|
var trackCodec = pickMostCompleteCodecName(codec, levelCodec);
|
18751
18774
|
var nextCodec = (_trackCodec = trackCodec) == null ? undefined : _trackCodec.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');
|
18752
|
-
if (trackCodec &&
|
18775
|
+
if (trackCodec && currentCodecFull && currentCodec !== nextCodec) {
|
18753
18776
|
if (trackName.slice(0, 5) === 'audio') {
|
18754
18777
|
trackCodec = getCodecCompatibleName(trackCodec, _this9.appendSource);
|
18755
18778
|
}
|
@@ -23839,6 +23862,14 @@
|
|
23839
23862
|
return AssetListLoader;
|
23840
23863
|
}();
|
23841
23864
|
|
23865
|
+
function addEventListener(el, type, listener) {
|
23866
|
+
removeEventListener(el, type, listener);
|
23867
|
+
el.addEventListener(type, listener);
|
23868
|
+
}
|
23869
|
+
function removeEventListener(el, type, listener) {
|
23870
|
+
el.removeEventListener(type, listener);
|
23871
|
+
}
|
23872
|
+
|
23842
23873
|
function playWithCatch(media) {
|
23843
23874
|
media == null ? undefined : media.play().catch(function () {
|
23844
23875
|
/* no-op */
|
@@ -24154,24 +24185,23 @@
|
|
24154
24185
|
this.onScheduleUpdate = null;
|
24155
24186
|
};
|
24156
24187
|
_proto.onDestroying = function onDestroying() {
|
24157
|
-
var media = this.primaryMedia;
|
24188
|
+
var media = this.primaryMedia || this.media;
|
24158
24189
|
if (media) {
|
24159
24190
|
this.removeMediaListeners(media);
|
24160
24191
|
}
|
24161
24192
|
};
|
24162
24193
|
_proto.removeMediaListeners = function removeMediaListeners(media) {
|
24163
|
-
|
24164
|
-
|
24165
|
-
|
24166
|
-
|
24194
|
+
removeEventListener(media, 'play', this.onPlay);
|
24195
|
+
removeEventListener(media, 'pause', this.onPause);
|
24196
|
+
removeEventListener(media, 'seeking', this.onSeeking);
|
24197
|
+
removeEventListener(media, 'timeupdate', this.onTimeupdate);
|
24167
24198
|
};
|
24168
24199
|
_proto.onMediaAttaching = function onMediaAttaching(event, data) {
|
24169
24200
|
var media = this.media = data.media;
|
24170
|
-
this.
|
24171
|
-
|
24172
|
-
|
24173
|
-
|
24174
|
-
media.addEventListener('pause', this.onPause);
|
24201
|
+
addEventListener(media, 'seeking', this.onSeeking);
|
24202
|
+
addEventListener(media, 'timeupdate', this.onTimeupdate);
|
24203
|
+
addEventListener(media, 'play', this.onPlay);
|
24204
|
+
addEventListener(media, 'pause', this.onPause);
|
24175
24205
|
};
|
24176
24206
|
_proto.onMediaAttached = function onMediaAttached(event, data) {
|
24177
24207
|
var playingItem = this.playingItem;
|
@@ -24975,7 +25005,7 @@
|
|
24975
25005
|
var primary = this.hls;
|
24976
25006
|
var userConfig = primary.userConfig;
|
24977
25007
|
var videoPreference = userConfig.videoPreference;
|
24978
|
-
var currentLevel = primary.
|
25008
|
+
var currentLevel = primary.loadLevelObj || primary.levels[primary.currentLevel];
|
24979
25009
|
if (videoPreference || currentLevel) {
|
24980
25010
|
videoPreference = _extends({}, videoPreference);
|
24981
25011
|
if (currentLevel.videoCodec) {
|
@@ -25676,7 +25706,7 @@
|
|
25676
25706
|
}]);
|
25677
25707
|
}(Logger);
|
25678
25708
|
|
25679
|
-
var TICK_INTERVAL$
|
25709
|
+
var TICK_INTERVAL$2 = 500; // how often to tick in ms
|
25680
25710
|
|
25681
25711
|
var SubtitleStreamController = /*#__PURE__*/function (_BaseStreamController) {
|
25682
25712
|
function SubtitleStreamController(hls, fragmentTracker, keyLoader) {
|
@@ -25718,7 +25748,7 @@
|
|
25718
25748
|
_proto.startLoad = function startLoad(startPosition) {
|
25719
25749
|
this.stopLoad();
|
25720
25750
|
this.state = State.IDLE;
|
25721
|
-
this.setInterval(TICK_INTERVAL$
|
25751
|
+
this.setInterval(TICK_INTERVAL$2);
|
25722
25752
|
this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition;
|
25723
25753
|
this.tick();
|
25724
25754
|
};
|
@@ -25854,7 +25884,7 @@
|
|
25854
25884
|
this.mediaBuffer = null;
|
25855
25885
|
}
|
25856
25886
|
if (currentTrack && this.state !== State.STOPPED) {
|
25857
|
-
this.setInterval(TICK_INTERVAL$
|
25887
|
+
this.setInterval(TICK_INTERVAL$2);
|
25858
25888
|
}
|
25859
25889
|
}
|
25860
25890
|
|
@@ -30255,16 +30285,20 @@
|
|
30255
30285
|
frontBufferFlushThreshold: Infinity,
|
30256
30286
|
maxBufferSize: 60 * 1000 * 1000,
|
30257
30287
|
// used by stream-controller
|
30258
|
-
|
30288
|
+
maxFragLookUpTolerance: 0.25,
|
30259
30289
|
// used by stream-controller
|
30290
|
+
maxBufferHole: 0.1,
|
30291
|
+
// used by stream-controller and gap-controller
|
30292
|
+
detectStallWithCurrentTimeMs: 1250,
|
30293
|
+
// used by gap-controller
|
30260
30294
|
highBufferWatchdogPeriod: 2,
|
30261
|
-
// used by
|
30295
|
+
// used by gap-controller
|
30262
30296
|
nudgeOffset: 0.1,
|
30263
|
-
// used by
|
30297
|
+
// used by gap-controller
|
30264
30298
|
nudgeMaxRetry: 3,
|
30265
|
-
// used by
|
30266
|
-
|
30267
|
-
// used by
|
30299
|
+
// used by gap-controller
|
30300
|
+
nudgeOnVideoHole: true,
|
30301
|
+
// used by gap-controller
|
30268
30302
|
liveSyncDurationCount: 3,
|
30269
30303
|
// used by latency-controller
|
30270
30304
|
liveSyncOnStallIncrease: 1,
|
@@ -30363,7 +30397,6 @@
|
|
30363
30397
|
progressive: false,
|
30364
30398
|
lowLatencyMode: true,
|
30365
30399
|
cmcd: undefined,
|
30366
|
-
detectStallWithCurrentTimeMs: 1250,
|
30367
30400
|
enableDateRangeMetadataCues: true,
|
30368
30401
|
enableEmsgMetadataCues: true,
|
30369
30402
|
enableEmsgKLVMetadata: false,
|
@@ -30618,6 +30651,545 @@
|
|
30618
30651
|
}
|
30619
30652
|
}
|
30620
30653
|
|
30654
|
+
var MAX_START_GAP_JUMP = 2.0;
|
30655
|
+
var SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
30656
|
+
var SKIP_BUFFER_RANGE_START = 0.05;
|
30657
|
+
var TICK_INTERVAL$1 = 100;
|
30658
|
+
var GapController = /*#__PURE__*/function (_TaskLoop) {
|
30659
|
+
function GapController(hls, fragmentTracker) {
|
30660
|
+
var _this;
|
30661
|
+
_this = _TaskLoop.call(this, 'gap-controller', hls.logger) || this;
|
30662
|
+
_this.hls = null;
|
30663
|
+
_this.fragmentTracker = null;
|
30664
|
+
_this.media = null;
|
30665
|
+
_this.mediaSource = undefined;
|
30666
|
+
_this.nudgeRetry = 0;
|
30667
|
+
_this.stallReported = false;
|
30668
|
+
_this.stalled = null;
|
30669
|
+
_this.moved = false;
|
30670
|
+
_this.seeking = false;
|
30671
|
+
_this.buffered = {};
|
30672
|
+
_this.lastCurrentTime = 0;
|
30673
|
+
_this.ended = 0;
|
30674
|
+
_this.waiting = 0;
|
30675
|
+
_this.onMediaPlaying = function () {
|
30676
|
+
_this.ended = 0;
|
30677
|
+
_this.waiting = 0;
|
30678
|
+
};
|
30679
|
+
_this.onMediaWaiting = function () {
|
30680
|
+
var _this$media;
|
30681
|
+
if ((_this$media = _this.media) != null && _this$media.seeking) {
|
30682
|
+
return;
|
30683
|
+
}
|
30684
|
+
_this.waiting = self.performance.now();
|
30685
|
+
_this.tick();
|
30686
|
+
};
|
30687
|
+
_this.onMediaEnded = function () {
|
30688
|
+
if (_this.hls) {
|
30689
|
+
var _this$media2;
|
30690
|
+
// ended is set when triggering MEDIA_ENDED so that we do not trigger it again on stall or on tick with media.ended
|
30691
|
+
_this.ended = ((_this$media2 = _this.media) == null ? undefined : _this$media2.currentTime) || 1;
|
30692
|
+
_this.hls.trigger(Events.MEDIA_ENDED, {
|
30693
|
+
stalled: false
|
30694
|
+
});
|
30695
|
+
}
|
30696
|
+
};
|
30697
|
+
_this.hls = hls;
|
30698
|
+
_this.fragmentTracker = fragmentTracker;
|
30699
|
+
_this.registerListeners();
|
30700
|
+
return _this;
|
30701
|
+
}
|
30702
|
+
_inheritsLoose(GapController, _TaskLoop);
|
30703
|
+
var _proto = GapController.prototype;
|
30704
|
+
_proto.registerListeners = function registerListeners() {
|
30705
|
+
var hls = this.hls;
|
30706
|
+
if (hls) {
|
30707
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
30708
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
30709
|
+
hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
|
30710
|
+
}
|
30711
|
+
};
|
30712
|
+
_proto.unregisterListeners = function unregisterListeners() {
|
30713
|
+
var hls = this.hls;
|
30714
|
+
if (hls) {
|
30715
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
30716
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
30717
|
+
hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
|
30718
|
+
}
|
30719
|
+
};
|
30720
|
+
_proto.destroy = function destroy() {
|
30721
|
+
_TaskLoop.prototype.destroy.call(this);
|
30722
|
+
this.unregisterListeners();
|
30723
|
+
this.media = this.hls = this.fragmentTracker = null;
|
30724
|
+
this.mediaSource = undefined;
|
30725
|
+
};
|
30726
|
+
_proto.onMediaAttached = function onMediaAttached(event, data) {
|
30727
|
+
this.setInterval(TICK_INTERVAL$1);
|
30728
|
+
this.mediaSource = data.mediaSource;
|
30729
|
+
var media = this.media = data.media;
|
30730
|
+
addEventListener(media, 'playing', this.onMediaPlaying);
|
30731
|
+
addEventListener(media, 'waiting', this.onMediaWaiting);
|
30732
|
+
addEventListener(media, 'ended', this.onMediaEnded);
|
30733
|
+
};
|
30734
|
+
_proto.onMediaDetaching = function onMediaDetaching(event, data) {
|
30735
|
+
this.clearInterval();
|
30736
|
+
var media = this.media;
|
30737
|
+
if (media) {
|
30738
|
+
removeEventListener(media, 'playing', this.onMediaPlaying);
|
30739
|
+
removeEventListener(media, 'waiting', this.onMediaWaiting);
|
30740
|
+
removeEventListener(media, 'ended', this.onMediaEnded);
|
30741
|
+
this.media = null;
|
30742
|
+
}
|
30743
|
+
this.mediaSource = undefined;
|
30744
|
+
};
|
30745
|
+
_proto.onBufferAppended = function onBufferAppended(event, data) {
|
30746
|
+
this.buffered = data.timeRanges;
|
30747
|
+
};
|
30748
|
+
_proto.tick = function tick() {
|
30749
|
+
var _this$media3;
|
30750
|
+
if (!((_this$media3 = this.media) != null && _this$media3.readyState) || !this.hasBuffered) {
|
30751
|
+
return;
|
30752
|
+
}
|
30753
|
+
var currentTime = this.media.currentTime;
|
30754
|
+
this.poll(currentTime, this.lastCurrentTime);
|
30755
|
+
this.lastCurrentTime = currentTime;
|
30756
|
+
}
|
30757
|
+
|
30758
|
+
/**
|
30759
|
+
* Checks if the playhead is stuck within a gap, and if so, attempts to free it.
|
30760
|
+
* A gap is an unbuffered range between two buffered ranges (or the start and the first buffered range).
|
30761
|
+
*
|
30762
|
+
* @param lastCurrentTime - Previously read playhead position
|
30763
|
+
*/;
|
30764
|
+
_proto.poll = function poll(currentTime, lastCurrentTime) {
|
30765
|
+
var _this$hls, _this$hls2;
|
30766
|
+
var config = (_this$hls = this.hls) == null ? undefined : _this$hls.config;
|
30767
|
+
if (!config) {
|
30768
|
+
return;
|
30769
|
+
}
|
30770
|
+
var media = this.media,
|
30771
|
+
stalled = this.stalled;
|
30772
|
+
if (!media) {
|
30773
|
+
return;
|
30774
|
+
}
|
30775
|
+
var seeking = media.seeking;
|
30776
|
+
var seeked = this.seeking && !seeking;
|
30777
|
+
var beginSeek = !this.seeking && seeking;
|
30778
|
+
var pausedEndedOrHalted = media.paused && !seeking || media.ended || media.playbackRate === 0;
|
30779
|
+
this.seeking = seeking;
|
30780
|
+
|
30781
|
+
// The playhead is moving, no-op
|
30782
|
+
if (currentTime !== lastCurrentTime) {
|
30783
|
+
if (lastCurrentTime) {
|
30784
|
+
this.ended = 0;
|
30785
|
+
}
|
30786
|
+
this.moved = true;
|
30787
|
+
if (!seeking) {
|
30788
|
+
this.nudgeRetry = 0;
|
30789
|
+
// When crossing between buffered video time ranges, but not audio, flush pipeline with seek (Chrome)
|
30790
|
+
if (config.nudgeOnVideoHole && !pausedEndedOrHalted && currentTime > lastCurrentTime) {
|
30791
|
+
this.nudgeOnVideoHole(currentTime, lastCurrentTime);
|
30792
|
+
}
|
30793
|
+
}
|
30794
|
+
if (this.waiting === 0) {
|
30795
|
+
this.stallResolved(currentTime);
|
30796
|
+
}
|
30797
|
+
return;
|
30798
|
+
}
|
30799
|
+
|
30800
|
+
// Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek
|
30801
|
+
if (beginSeek || seeked) {
|
30802
|
+
if (seeked) {
|
30803
|
+
this.stallResolved(currentTime);
|
30804
|
+
}
|
30805
|
+
return;
|
30806
|
+
}
|
30807
|
+
|
30808
|
+
// The playhead should not be moving
|
30809
|
+
if (pausedEndedOrHalted) {
|
30810
|
+
this.nudgeRetry = 0;
|
30811
|
+
this.stallResolved(currentTime);
|
30812
|
+
// Fire MEDIA_ENDED to workaround event not being dispatched by browser
|
30813
|
+
if (!this.ended && media.ended && this.hls) {
|
30814
|
+
this.ended = currentTime || 1;
|
30815
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
30816
|
+
stalled: false
|
30817
|
+
});
|
30818
|
+
}
|
30819
|
+
return;
|
30820
|
+
}
|
30821
|
+
if (!BufferHelper.getBuffered(media).length) {
|
30822
|
+
this.nudgeRetry = 0;
|
30823
|
+
return;
|
30824
|
+
}
|
30825
|
+
|
30826
|
+
// Resolve stalls at buffer holes using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
30827
|
+
var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
|
30828
|
+
var nextStart = bufferInfo.nextStart || 0;
|
30829
|
+
var fragmentTracker = this.fragmentTracker;
|
30830
|
+
if (seeking && fragmentTracker && this.hls) {
|
30831
|
+
// Is there a fragment loading/parsing/appending before currentTime?
|
30832
|
+
var inFlightDependency = getInFlightDependency(this.hls.inFlightFragments, currentTime);
|
30833
|
+
|
30834
|
+
// Waiting for seeking in a buffered range to complete
|
30835
|
+
var hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
|
30836
|
+
// Next buffered range is too far ahead to jump to while still seeking
|
30837
|
+
var noBufferHole = !nextStart || inFlightDependency || nextStart - currentTime > MAX_START_GAP_JUMP && !fragmentTracker.getPartialFragment(currentTime);
|
30838
|
+
if (hasEnoughBuffer || noBufferHole) {
|
30839
|
+
return;
|
30840
|
+
}
|
30841
|
+
// Reset moved state when seeking to a point in or before a gap/hole
|
30842
|
+
this.moved = false;
|
30843
|
+
}
|
30844
|
+
|
30845
|
+
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
30846
|
+
// The addition poll gives the browser a chance to jump the gap for us
|
30847
|
+
var levelDetails = (_this$hls2 = this.hls) == null ? undefined : _this$hls2.latestLevelDetails;
|
30848
|
+
if (!this.moved && this.stalled !== null && fragmentTracker) {
|
30849
|
+
// There is no playable buffer (seeked, waiting for buffer)
|
30850
|
+
var isBuffered = bufferInfo.len > 0;
|
30851
|
+
if (!isBuffered && !nextStart) {
|
30852
|
+
return;
|
30853
|
+
}
|
30854
|
+
// Jump start gaps within jump threshold
|
30855
|
+
var startJump = Math.max(nextStart, bufferInfo.start || 0) - currentTime;
|
30856
|
+
|
30857
|
+
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
30858
|
+
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
30859
|
+
// that begins over 1 target duration after the video start position.
|
30860
|
+
var isLive = !!(levelDetails != null && levelDetails.live);
|
30861
|
+
var maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
|
30862
|
+
var partialOrGap = fragmentTracker.getPartialFragment(currentTime);
|
30863
|
+
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
30864
|
+
if (!media.paused) {
|
30865
|
+
this._trySkipBufferHole(partialOrGap);
|
30866
|
+
}
|
30867
|
+
return;
|
30868
|
+
}
|
30869
|
+
}
|
30870
|
+
|
30871
|
+
// Start tracking stall time
|
30872
|
+
var detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs;
|
30873
|
+
var tnow = self.performance.now();
|
30874
|
+
var tWaiting = this.waiting;
|
30875
|
+
if (stalled === null) {
|
30876
|
+
// Use time of recent "waiting" event
|
30877
|
+
if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) {
|
30878
|
+
this.stalled = tWaiting;
|
30879
|
+
} else {
|
30880
|
+
this.stalled = tnow;
|
30881
|
+
}
|
30882
|
+
return;
|
30883
|
+
}
|
30884
|
+
var stalledDuration = tnow - stalled;
|
30885
|
+
if (!seeking && (stalledDuration >= detectStallWithCurrentTimeMs || tWaiting) && this.hls) {
|
30886
|
+
var _this$mediaSource;
|
30887
|
+
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
30888
|
+
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) {
|
30889
|
+
if (this.ended) {
|
30890
|
+
return;
|
30891
|
+
}
|
30892
|
+
this.ended = currentTime || 1;
|
30893
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
30894
|
+
stalled: true
|
30895
|
+
});
|
30896
|
+
return;
|
30897
|
+
}
|
30898
|
+
// Report stalling after trying to fix
|
30899
|
+
this._reportStall(bufferInfo);
|
30900
|
+
if (!this.media || !this.hls) {
|
30901
|
+
return;
|
30902
|
+
}
|
30903
|
+
}
|
30904
|
+
var bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole);
|
30905
|
+
this._tryFixBufferStall(bufferedWithHoles, stalledDuration);
|
30906
|
+
};
|
30907
|
+
_proto.stallResolved = function stallResolved(currentTime) {
|
30908
|
+
var stalled = this.stalled;
|
30909
|
+
if (stalled && this.hls) {
|
30910
|
+
this.stalled = null;
|
30911
|
+
// The playhead is now moving, but was previously stalled
|
30912
|
+
if (this.stallReported) {
|
30913
|
+
var stalledDuration = self.performance.now() - stalled;
|
30914
|
+
this.log("playback not stuck anymore @" + currentTime + ", after " + Math.round(stalledDuration) + "ms");
|
30915
|
+
this.stallReported = false;
|
30916
|
+
this.waiting = 0;
|
30917
|
+
this.hls.trigger(Events.STALL_RESOLVED, {});
|
30918
|
+
}
|
30919
|
+
}
|
30920
|
+
};
|
30921
|
+
_proto.nudgeOnVideoHole = function nudgeOnVideoHole(currentTime, lastCurrentTime) {
|
30922
|
+
var _this$buffered$audio;
|
30923
|
+
// 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:
|
30924
|
+
// https://github.com/video-dev/hls.js/issues/5631
|
30925
|
+
// https://issues.chromium.org/issues/40280613#comment10
|
30926
|
+
// Detect the potential for this situation and proactively seek to flush the video pipeline once the playhead passes the start of the video hole.
|
30927
|
+
// When there are audio and video buffers and currentTime is past the end of the first video buffered range...
|
30928
|
+
var videoSourceBuffered = this.buffered.video;
|
30929
|
+
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)) {
|
30930
|
+
// and audio is buffered at the playhead
|
30931
|
+
var audioBufferInfo = BufferHelper.bufferedInfo(BufferHelper.timeRangesToArray(this.buffered.audio), currentTime, 0);
|
30932
|
+
if (audioBufferInfo.len > 1 && lastCurrentTime >= audioBufferInfo.start) {
|
30933
|
+
var videoTimes = BufferHelper.timeRangesToArray(videoSourceBuffered);
|
30934
|
+
var lastBufferedIndex = BufferHelper.bufferedInfo(videoTimes, lastCurrentTime, 0).bufferedIndex;
|
30935
|
+
// nudge when crossing into another video buffered range (hole).
|
30936
|
+
if (lastBufferedIndex > -1 && lastBufferedIndex < videoTimes.length - 1) {
|
30937
|
+
var bufferedIndex = BufferHelper.bufferedInfo(videoTimes, currentTime, 0).bufferedIndex;
|
30938
|
+
var holeStart = videoTimes[lastBufferedIndex].end;
|
30939
|
+
var holeEnd = videoTimes[lastBufferedIndex + 1].start;
|
30940
|
+
if ((bufferedIndex === -1 || bufferedIndex > lastBufferedIndex) && holeEnd - holeStart < 1 &&
|
30941
|
+
// `maxBufferHole` may be too small and setting it to 0 should not disable this feature
|
30942
|
+
currentTime - holeStart < 2) {
|
30943
|
+
var error = new Error("nudging playhead to flush pipeline after video hole. currentTime: " + currentTime + " hole: " + holeStart + " -> " + holeEnd + " buffered index: " + bufferedIndex);
|
30944
|
+
this.warn(error.message);
|
30945
|
+
// Magic number to flush the pipeline without interuption to audio playback:
|
30946
|
+
this.media.currentTime += 0.000001;
|
30947
|
+
var frag = this.fragmentTracker.getPartialFragment(currentTime) || undefined;
|
30948
|
+
var bufferInfo = BufferHelper.bufferInfo(this.media, currentTime, 0);
|
30949
|
+
this.hls.trigger(Events.ERROR, {
|
30950
|
+
type: ErrorTypes.MEDIA_ERROR,
|
30951
|
+
details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
|
30952
|
+
fatal: false,
|
30953
|
+
error: error,
|
30954
|
+
reason: error.message,
|
30955
|
+
frag: frag,
|
30956
|
+
buffer: bufferInfo.len,
|
30957
|
+
bufferInfo: bufferInfo
|
30958
|
+
});
|
30959
|
+
}
|
30960
|
+
}
|
30961
|
+
}
|
30962
|
+
}
|
30963
|
+
}
|
30964
|
+
|
30965
|
+
/**
|
30966
|
+
* Detects and attempts to fix known buffer stalling issues.
|
30967
|
+
* @param bufferInfo - The properties of the current buffer.
|
30968
|
+
* @param stalledDurationMs - The amount of time Hls.js has been stalling for.
|
30969
|
+
* @private
|
30970
|
+
*/;
|
30971
|
+
_proto._tryFixBufferStall = function _tryFixBufferStall(bufferInfo, stalledDurationMs) {
|
30972
|
+
var _this$hls3;
|
30973
|
+
var fragmentTracker = this.fragmentTracker,
|
30974
|
+
media = this.media;
|
30975
|
+
var config = (_this$hls3 = this.hls) == null ? undefined : _this$hls3.config;
|
30976
|
+
if (!media || !fragmentTracker || !config) {
|
30977
|
+
return;
|
30978
|
+
}
|
30979
|
+
var currentTime = media.currentTime;
|
30980
|
+
var partial = fragmentTracker.getPartialFragment(currentTime);
|
30981
|
+
if (partial) {
|
30982
|
+
// Try to skip over the buffer hole caused by a partial fragment
|
30983
|
+
// This method isn't limited by the size of the gap between buffered ranges
|
30984
|
+
var targetTime = this._trySkipBufferHole(partial);
|
30985
|
+
// we return here in this case, meaning
|
30986
|
+
// the branch below only executes when we haven't seeked to a new position
|
30987
|
+
if (targetTime || !this.media) {
|
30988
|
+
return;
|
30989
|
+
}
|
30990
|
+
}
|
30991
|
+
|
30992
|
+
// if we haven't had to skip over a buffer hole of a partial fragment
|
30993
|
+
// we may just have to "nudge" the playlist as the browser decoding/rendering engine
|
30994
|
+
// needs to cross some sort of threshold covering all source-buffers content
|
30995
|
+
// to start playing properly.
|
30996
|
+
var bufferedRanges = bufferInfo.buffered;
|
30997
|
+
if ((bufferedRanges && bufferedRanges.length > 1 && bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && (stalledDurationMs > config.highBufferWatchdogPeriod * 1000 || this.waiting)) {
|
30998
|
+
this.warn('Trying to nudge playhead over buffer-hole');
|
30999
|
+
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
31000
|
+
// We only try to jump the hole if it's under the configured size
|
31001
|
+
this._tryNudgeBuffer(bufferInfo);
|
31002
|
+
}
|
31003
|
+
}
|
31004
|
+
|
31005
|
+
/**
|
31006
|
+
* Triggers a BUFFER_STALLED_ERROR event, but only once per stall period.
|
31007
|
+
* @param bufferLen - The playhead distance from the end of the current buffer segment.
|
31008
|
+
* @private
|
31009
|
+
*/;
|
31010
|
+
_proto._reportStall = function _reportStall(bufferInfo) {
|
31011
|
+
var hls = this.hls,
|
31012
|
+
media = this.media,
|
31013
|
+
stallReported = this.stallReported,
|
31014
|
+
stalled = this.stalled;
|
31015
|
+
if (!stallReported && stalled !== null && media && hls) {
|
31016
|
+
// Report stalled error once
|
31017
|
+
this.stallReported = true;
|
31018
|
+
var error = new Error("Playback stalling at @" + media.currentTime + " due to low buffer (" + JSON.stringify(bufferInfo) + ")");
|
31019
|
+
this.warn(error.message);
|
31020
|
+
hls.trigger(Events.ERROR, {
|
31021
|
+
type: ErrorTypes.MEDIA_ERROR,
|
31022
|
+
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
31023
|
+
fatal: false,
|
31024
|
+
error: error,
|
31025
|
+
buffer: bufferInfo.len,
|
31026
|
+
bufferInfo: bufferInfo,
|
31027
|
+
stalled: {
|
31028
|
+
start: stalled
|
31029
|
+
}
|
31030
|
+
});
|
31031
|
+
}
|
31032
|
+
}
|
31033
|
+
|
31034
|
+
/**
|
31035
|
+
* Attempts to fix buffer stalls by jumping over known gaps caused by partial fragments
|
31036
|
+
* @param partial - The partial fragment found at the current time (where playback is stalling).
|
31037
|
+
* @private
|
31038
|
+
*/;
|
31039
|
+
_proto._trySkipBufferHole = function _trySkipBufferHole(partial) {
|
31040
|
+
var _this$hls4;
|
31041
|
+
var fragmentTracker = this.fragmentTracker,
|
31042
|
+
media = this.media;
|
31043
|
+
var config = (_this$hls4 = this.hls) == null ? undefined : _this$hls4.config;
|
31044
|
+
if (!media || !fragmentTracker || !config) {
|
31045
|
+
return 0;
|
31046
|
+
}
|
31047
|
+
|
31048
|
+
// Check if currentTime is between unbuffered regions of partial fragments
|
31049
|
+
var currentTime = media.currentTime;
|
31050
|
+
var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
|
31051
|
+
var startTime = currentTime < bufferInfo.start ? bufferInfo.start : bufferInfo.nextStart;
|
31052
|
+
if (startTime && this.hls) {
|
31053
|
+
var bufferStarved = bufferInfo.len <= config.maxBufferHole;
|
31054
|
+
var waiting = bufferInfo.len > 0 && bufferInfo.len < 1 && media.readyState < 3;
|
31055
|
+
var gapLength = startTime - currentTime;
|
31056
|
+
if (gapLength > 0 && (bufferStarved || waiting)) {
|
31057
|
+
// Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial
|
31058
|
+
if (gapLength > config.maxBufferHole) {
|
31059
|
+
var startGap = false;
|
31060
|
+
if (currentTime === 0) {
|
31061
|
+
var startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN);
|
31062
|
+
if (startFrag && startTime < startFrag.end) {
|
31063
|
+
startGap = true;
|
31064
|
+
}
|
31065
|
+
}
|
31066
|
+
if (!startGap) {
|
31067
|
+
var startProvisioned = partial || fragmentTracker.getAppendedFrag(currentTime, PlaylistLevelType.MAIN);
|
31068
|
+
if (startProvisioned) {
|
31069
|
+
var _this$hls$loadLevelOb;
|
31070
|
+
// Do not seek when selected variant playlist is unloaded
|
31071
|
+
if (!((_this$hls$loadLevelOb = this.hls.loadLevelObj) != null && _this$hls$loadLevelOb.details)) {
|
31072
|
+
return 0;
|
31073
|
+
}
|
31074
|
+
// Do not seek when required fragments are inflight or appending
|
31075
|
+
var inFlightDependency = getInFlightDependency(this.hls.inFlightFragments, startTime);
|
31076
|
+
if (inFlightDependency) {
|
31077
|
+
return 0;
|
31078
|
+
}
|
31079
|
+
// Do not seek if we can't walk tracked fragments to end of gap
|
31080
|
+
var moreToLoad = false;
|
31081
|
+
var pos = startProvisioned.end;
|
31082
|
+
while (pos < startTime) {
|
31083
|
+
var provisioned = fragmentTracker.getPartialFragment(pos);
|
31084
|
+
if (provisioned) {
|
31085
|
+
pos += provisioned.duration;
|
31086
|
+
} else {
|
31087
|
+
moreToLoad = true;
|
31088
|
+
break;
|
31089
|
+
}
|
31090
|
+
}
|
31091
|
+
if (moreToLoad) {
|
31092
|
+
return 0;
|
31093
|
+
}
|
31094
|
+
}
|
31095
|
+
}
|
31096
|
+
}
|
31097
|
+
var targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
31098
|
+
this.warn("skipping hole, adjusting currentTime from " + currentTime + " to " + targetTime);
|
31099
|
+
this.moved = true;
|
31100
|
+
media.currentTime = targetTime;
|
31101
|
+
if (!(partial != null && partial.gap)) {
|
31102
|
+
var error = new Error("fragment loaded with buffer holes, seeking from " + currentTime + " to " + targetTime);
|
31103
|
+
this.hls.trigger(Events.ERROR, {
|
31104
|
+
type: ErrorTypes.MEDIA_ERROR,
|
31105
|
+
details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
|
31106
|
+
fatal: false,
|
31107
|
+
error: error,
|
31108
|
+
reason: error.message,
|
31109
|
+
frag: partial || undefined,
|
31110
|
+
buffer: bufferInfo.len,
|
31111
|
+
bufferInfo: bufferInfo
|
31112
|
+
});
|
31113
|
+
}
|
31114
|
+
return targetTime;
|
31115
|
+
}
|
31116
|
+
}
|
31117
|
+
return 0;
|
31118
|
+
}
|
31119
|
+
|
31120
|
+
/**
|
31121
|
+
* Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount.
|
31122
|
+
* @private
|
31123
|
+
*/;
|
31124
|
+
_proto._tryNudgeBuffer = function _tryNudgeBuffer(bufferInfo) {
|
31125
|
+
var hls = this.hls,
|
31126
|
+
media = this.media,
|
31127
|
+
nudgeRetry = this.nudgeRetry;
|
31128
|
+
var config = hls == null ? undefined : hls.config;
|
31129
|
+
if (!media || !config) {
|
31130
|
+
return 0;
|
31131
|
+
}
|
31132
|
+
var currentTime = media.currentTime;
|
31133
|
+
this.nudgeRetry++;
|
31134
|
+
if (nudgeRetry < config.nudgeMaxRetry) {
|
31135
|
+
var targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
31136
|
+
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
31137
|
+
var error = new Error("Nudging 'currentTime' from " + currentTime + " to " + targetTime);
|
31138
|
+
this.warn(error.message);
|
31139
|
+
media.currentTime = targetTime;
|
31140
|
+
hls.trigger(Events.ERROR, {
|
31141
|
+
type: ErrorTypes.MEDIA_ERROR,
|
31142
|
+
details: ErrorDetails.BUFFER_NUDGE_ON_STALL,
|
31143
|
+
error: error,
|
31144
|
+
fatal: false,
|
31145
|
+
buffer: bufferInfo.len,
|
31146
|
+
bufferInfo: bufferInfo
|
31147
|
+
});
|
31148
|
+
} else {
|
31149
|
+
var _error = new Error("Playhead still not moving while enough data buffered @" + currentTime + " after " + config.nudgeMaxRetry + " nudges");
|
31150
|
+
this.error(_error.message);
|
31151
|
+
hls.trigger(Events.ERROR, {
|
31152
|
+
type: ErrorTypes.MEDIA_ERROR,
|
31153
|
+
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
31154
|
+
error: _error,
|
31155
|
+
fatal: true,
|
31156
|
+
buffer: bufferInfo.len,
|
31157
|
+
bufferInfo: bufferInfo
|
31158
|
+
});
|
31159
|
+
}
|
31160
|
+
};
|
31161
|
+
return _createClass(GapController, [{
|
31162
|
+
key: "hasBuffered",
|
31163
|
+
get: function get() {
|
31164
|
+
return Object.keys(this.buffered).length > 0;
|
31165
|
+
}
|
31166
|
+
}]);
|
31167
|
+
}(TaskLoop);
|
31168
|
+
function getInFlightDependency(inFlightFragments, currentTime) {
|
31169
|
+
var main = inFlight(inFlightFragments.main);
|
31170
|
+
if (main && main.start <= currentTime) {
|
31171
|
+
return main;
|
31172
|
+
}
|
31173
|
+
var audio = inFlight(inFlightFragments.audio);
|
31174
|
+
if (audio && audio.start <= currentTime) {
|
31175
|
+
return audio;
|
31176
|
+
}
|
31177
|
+
return null;
|
31178
|
+
}
|
31179
|
+
function inFlight(inFlightData) {
|
31180
|
+
if (!inFlightData) {
|
31181
|
+
return null;
|
31182
|
+
}
|
31183
|
+
switch (inFlightData.state) {
|
31184
|
+
case State.IDLE:
|
31185
|
+
case State.STOPPED:
|
31186
|
+
case State.ENDED:
|
31187
|
+
case State.ERROR:
|
31188
|
+
return null;
|
31189
|
+
}
|
31190
|
+
return inFlightData.frag;
|
31191
|
+
}
|
31192
|
+
|
30621
31193
|
var MIN_CUE_DURATION = 0.25;
|
30622
31194
|
function getCueClass() {
|
30623
31195
|
if (typeof self === 'undefined') return undefined;
|
@@ -31630,6 +32202,11 @@
|
|
31630
32202
|
}
|
31631
32203
|
return this._levels;
|
31632
32204
|
}
|
32205
|
+
}, {
|
32206
|
+
key: "loadLevelObj",
|
32207
|
+
get: function get() {
|
32208
|
+
return this.currentLevel;
|
32209
|
+
}
|
31633
32210
|
}, {
|
31634
32211
|
key: "level",
|
31635
32212
|
get: function get() {
|
@@ -31789,375 +32366,6 @@
|
|
31789
32366
|
});
|
31790
32367
|
}
|
31791
32368
|
|
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
32369
|
function getSourceBuffer() {
|
32162
32370
|
return self.SourceBuffer || self.WebKitSourceBuffer;
|
32163
32371
|
}
|
@@ -32195,7 +32403,6 @@
|
|
32195
32403
|
var _this;
|
32196
32404
|
_this = _BaseStreamController.call(this, hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN) || this;
|
32197
32405
|
_this.audioCodecSwap = false;
|
32198
|
-
_this.gapController = null;
|
32199
32406
|
_this.level = -1;
|
32200
32407
|
_this._forceStartLoad = false;
|
32201
32408
|
_this._hasEnoughToStart = false;
|
@@ -32207,19 +32414,8 @@
|
|
32207
32414
|
_this.backtrackFragment = null;
|
32208
32415
|
_this.audioCodecSwitch = false;
|
32209
32416
|
_this.videoBuffer = null;
|
32210
|
-
_this.onMediaWaiting = function () {
|
32211
|
-
var gapController = _this.gapController;
|
32212
|
-
if (gapController) {
|
32213
|
-
gapController.waiting = self.performance.now();
|
32214
|
-
}
|
32215
|
-
};
|
32216
32417
|
_this.onMediaPlaying = function () {
|
32217
32418
|
// 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
32419
|
_this.tick();
|
32224
32420
|
};
|
32225
32421
|
_this.onMediaSeeked = function () {
|
@@ -32273,7 +32469,7 @@
|
|
32273
32469
|
};
|
32274
32470
|
_proto.onHandlerDestroying = function onHandlerDestroying() {
|
32275
32471
|
// @ts-ignore
|
32276
|
-
this.onMediaPlaying = this.onMediaSeeked =
|
32472
|
+
this.onMediaPlaying = this.onMediaSeeked = null;
|
32277
32473
|
this.unregisterListeners();
|
32278
32474
|
_BaseStreamController.prototype.onHandlerDestroying.call(this);
|
32279
32475
|
};
|
@@ -32362,8 +32558,11 @@
|
|
32362
32558
|
this.onTickEnd();
|
32363
32559
|
};
|
32364
32560
|
_proto.onTickEnd = function onTickEnd() {
|
32561
|
+
var _this$media2;
|
32365
32562
|
_BaseStreamController.prototype.onTickEnd.call(this);
|
32366
|
-
this.
|
32563
|
+
if ((_this$media2 = this.media) != null && _this$media2.readyState && this.media.seeking === false) {
|
32564
|
+
this.lastCurrentTime = this.media.currentTime;
|
32565
|
+
}
|
32367
32566
|
this.checkFragmentChanged();
|
32368
32567
|
};
|
32369
32568
|
_proto.doTickIdle = function doTickIdle() {
|
@@ -32592,27 +32791,17 @@
|
|
32592
32791
|
_proto.onMediaAttached = function onMediaAttached(event, data) {
|
32593
32792
|
_BaseStreamController.prototype.onMediaAttached.call(this, event, data);
|
32594
32793
|
var media = data.media;
|
32595
|
-
media
|
32596
|
-
media
|
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);
|
32794
|
+
addEventListener(media, 'playing', this.onMediaPlaying);
|
32795
|
+
addEventListener(media, 'seeked', this.onMediaSeeked);
|
32602
32796
|
};
|
32603
32797
|
_proto.onMediaDetaching = function onMediaDetaching(event, data) {
|
32604
32798
|
var media = this.media;
|
32605
32799
|
if (media) {
|
32606
|
-
|
32607
|
-
|
32608
|
-
media.removeEventListener('waiting', this.onMediaWaiting);
|
32800
|
+
removeEventListener(media, 'playing', this.onMediaPlaying);
|
32801
|
+
removeEventListener(media, 'seeked', this.onMediaSeeked);
|
32609
32802
|
}
|
32610
32803
|
this.videoBuffer = null;
|
32611
32804
|
this.fragPlaying = null;
|
32612
|
-
if (this.gapController) {
|
32613
|
-
this.gapController.destroy();
|
32614
|
-
this.gapController = null;
|
32615
|
-
}
|
32616
32805
|
_BaseStreamController.prototype.onMediaDetaching.call(this, event, data);
|
32617
32806
|
var transferringMedia = !!data.transferMedia;
|
32618
32807
|
if (transferringMedia) {
|
@@ -32620,19 +32809,6 @@
|
|
32620
32809
|
}
|
32621
32810
|
this._hasEnoughToStart = false;
|
32622
32811
|
};
|
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
32812
|
_proto.onManifestLoading = function onManifestLoading() {
|
32637
32813
|
_BaseStreamController.prototype.onManifestLoading.call(this);
|
32638
32814
|
// reset buffer on manifest loading
|
@@ -32952,25 +33128,6 @@
|
|
32952
33128
|
this.recoverWorkerError(data);
|
32953
33129
|
break;
|
32954
33130
|
}
|
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
33131
|
};
|
32975
33132
|
_proto.onFragLoadEmergencyAborted = function onFragLoadEmergencyAborted() {
|
32976
33133
|
this.state = State.IDLE;
|
@@ -32986,8 +33143,10 @@
|
|
32986
33143
|
var type = _ref.type;
|
32987
33144
|
if (type !== ElementaryStreamTypes.AUDIO || !this.altAudio) {
|
32988
33145
|
var mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media;
|
32989
|
-
|
32990
|
-
|
33146
|
+
if (mediaBuffer) {
|
33147
|
+
this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN);
|
33148
|
+
this.tick();
|
33149
|
+
}
|
32991
33150
|
}
|
32992
33151
|
};
|
32993
33152
|
_proto.onLevelsUpdated = function onLevelsUpdated(event, data) {
|
@@ -34306,9 +34465,12 @@
|
|
34306
34465
|
this.latencyController = undefined;
|
34307
34466
|
this.levelController = undefined;
|
34308
34467
|
this.streamController = undefined;
|
34468
|
+
this.audioStreamController = undefined;
|
34469
|
+
this.subtititleStreamController = undefined;
|
34309
34470
|
this.audioTrackController = undefined;
|
34310
34471
|
this.subtitleTrackController = undefined;
|
34311
34472
|
this.interstitialsController = undefined;
|
34473
|
+
this.gapController = undefined;
|
34312
34474
|
this.emeController = undefined;
|
34313
34475
|
this.cmcdController = undefined;
|
34314
34476
|
this._media = null;
|
@@ -34346,6 +34508,7 @@
|
|
34346
34508
|
var id3TrackController = new ID3TrackController(this);
|
34347
34509
|
var keyLoader = new KeyLoader(this.config);
|
34348
34510
|
var streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader);
|
34511
|
+
var gapController = this.gapController = new GapController(this, fragmentTracker);
|
34349
34512
|
|
34350
34513
|
// Cap level controller uses streamController to flush the buffer
|
34351
34514
|
capLevelController.setStreamController(streamController);
|
@@ -34359,17 +34522,17 @@
|
|
34359
34522
|
networkControllers.splice(1, 0, contentSteering);
|
34360
34523
|
}
|
34361
34524
|
this.networkControllers = networkControllers;
|
34362
|
-
var coreComponents = [abrController, bufferController, capLevelController, fpsController, id3TrackController, fragmentTracker];
|
34525
|
+
var coreComponents = [abrController, bufferController, gapController, capLevelController, fpsController, id3TrackController, fragmentTracker];
|
34363
34526
|
this.audioTrackController = this.createController(config.audioTrackController, networkControllers);
|
34364
34527
|
var AudioStreamControllerClass = config.audioStreamController;
|
34365
34528
|
if (AudioStreamControllerClass) {
|
34366
|
-
networkControllers.push(new AudioStreamControllerClass(this, fragmentTracker, keyLoader));
|
34529
|
+
networkControllers.push(this.audioStreamController = new AudioStreamControllerClass(this, fragmentTracker, keyLoader));
|
34367
34530
|
}
|
34368
34531
|
// Instantiate subtitleTrackController before SubtitleStreamController to receive level events first
|
34369
34532
|
this.subtitleTrackController = this.createController(config.subtitleTrackController, networkControllers);
|
34370
34533
|
var SubtitleStreamControllerClass = config.subtitleStreamController;
|
34371
34534
|
if (SubtitleStreamControllerClass) {
|
34372
|
-
networkControllers.push(new SubtitleStreamControllerClass(this, fragmentTracker, keyLoader));
|
34535
|
+
networkControllers.push(this.subtititleStreamController = new SubtitleStreamControllerClass(this, fragmentTracker, keyLoader));
|
34373
34536
|
}
|
34374
34537
|
this.createController(config.timelineController, coreComponents);
|
34375
34538
|
keyLoader.emeController = this.emeController = this.createController(config.emeController, coreComponents);
|
@@ -34644,11 +34807,10 @@
|
|
34644
34807
|
}
|
34645
34808
|
});
|
34646
34809
|
}
|
34647
|
-
}
|
34648
|
-
|
34810
|
+
};
|
34649
34811
|
/**
|
34650
34812
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
34651
|
-
|
34813
|
+
*/
|
34652
34814
|
_proto.swapAudioCodec = function swapAudioCodec() {
|
34653
34815
|
this.logger.log('swapAudioCodec');
|
34654
34816
|
this.streamController.swapAudioCodec();
|
@@ -34746,6 +34908,19 @@
|
|
34746
34908
|
get: function get() {
|
34747
34909
|
return this.streamController.bufferingEnabled;
|
34748
34910
|
}
|
34911
|
+
}, {
|
34912
|
+
key: "inFlightFragments",
|
34913
|
+
get: function get() {
|
34914
|
+
var _inFlightData;
|
34915
|
+
var inFlightData = (_inFlightData = {}, _inFlightData[PlaylistLevelType.MAIN] = this.streamController.inFlightFrag, _inFlightData);
|
34916
|
+
if (this.audioStreamController) {
|
34917
|
+
inFlightData[PlaylistLevelType.AUDIO] = this.audioStreamController.inFlightFrag;
|
34918
|
+
}
|
34919
|
+
if (this.subtititleStreamController) {
|
34920
|
+
inFlightData[PlaylistLevelType.SUBTITLE] = this.subtititleStreamController.inFlightFrag;
|
34921
|
+
}
|
34922
|
+
return inFlightData;
|
34923
|
+
}
|
34749
34924
|
}, {
|
34750
34925
|
key: "sessionId",
|
34751
34926
|
get: function get() {
|
@@ -34765,12 +34940,25 @@
|
|
34765
34940
|
var levels = this.levelController.levels;
|
34766
34941
|
return levels ? levels : [];
|
34767
34942
|
}
|
34943
|
+
|
34944
|
+
/**
|
34945
|
+
* @returns LevelDetails of last loaded level (variant) or `null` prior to loading a media playlist.
|
34946
|
+
*/
|
34768
34947
|
}, {
|
34769
34948
|
key: "latestLevelDetails",
|
34770
34949
|
get: function get() {
|
34771
34950
|
return this.streamController.getLevelDetails() || null;
|
34772
34951
|
}
|
34773
34952
|
|
34953
|
+
/**
|
34954
|
+
* @returns Level object of selected level (variant) or `null` prior to selecting a level or once the level is removed.
|
34955
|
+
*/
|
34956
|
+
}, {
|
34957
|
+
key: "loadLevelObj",
|
34958
|
+
get: function get() {
|
34959
|
+
return this.levelController.loadLevelObj;
|
34960
|
+
}
|
34961
|
+
|
34774
34962
|
/**
|
34775
34963
|
* Index of quality level (variant) currently played
|
34776
34964
|
*/
|