hls.js 1.6.0-beta.2.0.canary.10882 → 1.6.0-beta.2.0.canary.10884
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hls.d.mts +21 -1
- package/dist/hls.d.ts +21 -1
- package/dist/hls.js +130 -98
- package/dist/hls.js.d.ts +21 -1
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +130 -98
- 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 +123 -93
- 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 +123 -93
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +2 -0
- package/src/controller/gap-controller.ts +92 -58
- package/src/controller/stream-controller.ts +13 -3
- package/src/events.ts +3 -0
- package/src/hls.ts +2 -1
- package/src/loader/level-details.ts +0 -2
- package/src/loader/m3u8-parser.ts +1 -1
- package/src/types/events.ts +3 -0
- package/src/utils/buffer-helper.ts +5 -8
- package/src/utils/level-helper.ts +29 -35
package/dist/hls.mjs
CHANGED
@@ -77,6 +77,7 @@ let Events = /*#__PURE__*/function (Events) {
|
|
77
77
|
Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
|
78
78
|
Events["MEDIA_DETACHED"] = "hlsMediaDetached";
|
79
79
|
Events["MEDIA_ENDED"] = "hlsMediaEnded";
|
80
|
+
Events["STALL_RESOLVED"] = "hlsStallResolved";
|
80
81
|
Events["BUFFER_RESET"] = "hlsBufferReset";
|
81
82
|
Events["BUFFER_CODECS"] = "hlsBufferCodecs";
|
82
83
|
Events["BUFFER_CREATED"] = "hlsBufferCreated";
|
@@ -401,7 +402,7 @@ function enableLogs(debugConfig, context, id) {
|
|
401
402
|
// Some browsers don't allow to use bind on console object anyway
|
402
403
|
// fallback to default if needed
|
403
404
|
try {
|
404
|
-
newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.6.0-beta.2.0.canary.
|
405
|
+
newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.6.0-beta.2.0.canary.10884"}`);
|
405
406
|
} catch (e) {
|
406
407
|
/* log fn threw an exception. All logger methods are no-ops. */
|
407
408
|
return createLogger();
|
@@ -5862,8 +5863,7 @@ class BufferHelper {
|
|
5862
5863
|
return {
|
5863
5864
|
len: 0,
|
5864
5865
|
start: pos,
|
5865
|
-
end: pos
|
5866
|
-
nextStart: undefined
|
5866
|
+
end: pos
|
5867
5867
|
};
|
5868
5868
|
}
|
5869
5869
|
static bufferedInfo(buffered, pos, maxHoleDuration) {
|
@@ -5926,7 +5926,8 @@ class BufferHelper {
|
|
5926
5926
|
len: bufferLen,
|
5927
5927
|
start: bufferStart || 0,
|
5928
5928
|
end: bufferEnd || 0,
|
5929
|
-
nextStart: bufferStartNext
|
5929
|
+
nextStart: bufferStartNext,
|
5930
|
+
buffered
|
5930
5931
|
};
|
5931
5932
|
}
|
5932
5933
|
|
@@ -6294,8 +6295,6 @@ class LevelDetails {
|
|
6294
6295
|
this.advancedDateTime = undefined;
|
6295
6296
|
this.updated = true;
|
6296
6297
|
this.advanced = true;
|
6297
|
-
this.availabilityDelay = undefined;
|
6298
|
-
// Manifest reload synchronization
|
6299
6298
|
this.misses = 0;
|
6300
6299
|
this.startCC = 0;
|
6301
6300
|
this.startSN = 0;
|
@@ -6347,7 +6346,6 @@ class LevelDetails {
|
|
6347
6346
|
} else {
|
6348
6347
|
this.misses = previous.misses + 1;
|
6349
6348
|
}
|
6350
|
-
this.availabilityDelay = previous.availabilityDelay;
|
6351
6349
|
}
|
6352
6350
|
get hasProgramDateTime() {
|
6353
6351
|
if (this.fragments.length) {
|
@@ -7303,7 +7301,7 @@ class M3U8Parser {
|
|
7303
7301
|
if (!level.live) {
|
7304
7302
|
lastFragment.endList = true;
|
7305
7303
|
}
|
7306
|
-
if (firstFragment &&
|
7304
|
+
if (firstFragment && level.startCC === undefined) {
|
7307
7305
|
level.startCC = firstFragment.cc;
|
7308
7306
|
}
|
7309
7307
|
/**
|
@@ -7586,15 +7584,16 @@ function mergeDetails(oldDetails, newDetails) {
|
|
7586
7584
|
delete oldDetails.fragmentHint.endPTS;
|
7587
7585
|
}
|
7588
7586
|
// check if old/new playlists have fragments in common
|
7589
|
-
// loop through overlapping SN and update startPTS
|
7590
|
-
let ccOffset = 0;
|
7587
|
+
// loop through overlapping SN and update startPTS, cc, and duration if any found
|
7591
7588
|
let PTSFrag;
|
7592
|
-
mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag) => {
|
7593
|
-
if (
|
7594
|
-
|
7595
|
-
|
7596
|
-
|
7597
|
-
|
7589
|
+
mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag, newFragIndex, newFragments) => {
|
7590
|
+
if (newDetails.skippedSegments) {
|
7591
|
+
if (newFrag.cc !== oldFrag.cc) {
|
7592
|
+
const ccOffset = oldFrag.cc - newFrag.cc;
|
7593
|
+
for (let i = newFragIndex; i < newFragments.length; i++) {
|
7594
|
+
newFragments[i].cc += ccOffset;
|
7595
|
+
}
|
7596
|
+
}
|
7598
7597
|
}
|
7599
7598
|
if (isFiniteNumber(oldFrag.startPTS) && isFiniteNumber(oldFrag.endPTS)) {
|
7600
7599
|
newFrag.setStart(newFrag.startPTS = oldFrag.startPTS);
|
@@ -7623,7 +7622,8 @@ function mergeDetails(oldDetails, newDetails) {
|
|
7623
7622
|
currentInitSegment = oldFrag.initSegment;
|
7624
7623
|
}
|
7625
7624
|
});
|
7626
|
-
const
|
7625
|
+
const newFragments = newDetails.fragments;
|
7626
|
+
const fragmentsToCheck = newDetails.fragmentHint ? newFragments.concat(newDetails.fragmentHint) : newFragments;
|
7627
7627
|
if (currentInitSegment) {
|
7628
7628
|
fragmentsToCheck.forEach(frag => {
|
7629
7629
|
var _currentInitSegment;
|
@@ -7633,17 +7633,15 @@ function mergeDetails(oldDetails, newDetails) {
|
|
7633
7633
|
});
|
7634
7634
|
}
|
7635
7635
|
if (newDetails.skippedSegments) {
|
7636
|
-
newDetails.deltaUpdateFailed =
|
7636
|
+
newDetails.deltaUpdateFailed = newFragments.some(frag => !frag);
|
7637
7637
|
if (newDetails.deltaUpdateFailed) {
|
7638
7638
|
logger.warn('[level-helper] Previous playlist missing segments skipped in delta playlist');
|
7639
7639
|
for (let i = newDetails.skippedSegments; i--;) {
|
7640
|
-
|
7641
|
-
}
|
7642
|
-
newDetails.startSN = newDetails.fragments[0].sn;
|
7643
|
-
if (!newDetails.startCC) {
|
7644
|
-
newDetails.startCC = newDetails.fragments[0].cc;
|
7640
|
+
newFragments.shift();
|
7645
7641
|
}
|
7642
|
+
newDetails.startSN = newFragments[0].sn;
|
7646
7643
|
} else {
|
7644
|
+
newDetails.endCC = newFragments[newFragments.length - 1].cc;
|
7647
7645
|
if (newDetails.canSkipDateRanges) {
|
7648
7646
|
newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails);
|
7649
7647
|
}
|
@@ -7658,16 +7656,6 @@ function mergeDetails(oldDetails, newDetails) {
|
|
7658
7656
|
mapDateRanges(programDateTimes, newDetails);
|
7659
7657
|
}
|
7660
7658
|
}
|
7661
|
-
const newFragments = newDetails.fragments;
|
7662
|
-
if (ccOffset) {
|
7663
|
-
logger.warn('discontinuity sliding from playlist, take drift into account');
|
7664
|
-
for (let i = 0; i < newFragments.length; i++) {
|
7665
|
-
newFragments[i].cc += ccOffset;
|
7666
|
-
}
|
7667
|
-
}
|
7668
|
-
if (newDetails.skippedSegments) {
|
7669
|
-
newDetails.startCC = newDetails.fragments[0].cc;
|
7670
|
-
}
|
7671
7659
|
|
7672
7660
|
// Merge parts
|
7673
7661
|
mapPartIntersection(oldDetails.partList, newDetails.partList, (oldPart, newPart) => {
|
@@ -7765,7 +7753,7 @@ function mapFragmentIntersection(oldDetails, newDetails, intersectionFn) {
|
|
7765
7753
|
newFrag = newDetails.fragments[i] = oldFrag;
|
7766
7754
|
}
|
7767
7755
|
if (oldFrag && newFrag) {
|
7768
|
-
intersectionFn(oldFrag, newFrag);
|
7756
|
+
intersectionFn(oldFrag, newFrag, i, newFrags);
|
7769
7757
|
}
|
7770
7758
|
}
|
7771
7759
|
}
|
@@ -9847,7 +9835,7 @@ var eventemitter3 = {exports: {}};
|
|
9847
9835
|
var eventemitter3Exports = eventemitter3.exports;
|
9848
9836
|
var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports);
|
9849
9837
|
|
9850
|
-
const version = "1.6.0-beta.2.0.canary.
|
9838
|
+
const version = "1.6.0-beta.2.0.canary.10884";
|
9851
9839
|
|
9852
9840
|
// ensure the worker ends up in the bundle
|
9853
9841
|
// If the worker should not be included this gets aliased to empty.js
|
@@ -29443,6 +29431,7 @@ const hlsDefaultConfig = _objectSpread2(_objectSpread2({
|
|
29443
29431
|
progressive: false,
|
29444
29432
|
lowLatencyMode: true,
|
29445
29433
|
cmcd: undefined,
|
29434
|
+
detectStallWithCurrentTimeMs: 1250,
|
29446
29435
|
enableDateRangeMetadataCues: true,
|
29447
29436
|
enableEmsgMetadataCues: true,
|
29448
29437
|
enableEmsgKLVMetadata: false,
|
@@ -30851,32 +30840,28 @@ function assignTrackIdsByGroup(tracks) {
|
|
30851
30840
|
});
|
30852
30841
|
}
|
30853
30842
|
|
30854
|
-
const STALL_MINIMUM_DURATION_MS = 250;
|
30855
30843
|
const MAX_START_GAP_JUMP = 2.0;
|
30856
30844
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
30857
30845
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
30858
30846
|
class GapController extends Logger {
|
30859
|
-
constructor(
|
30847
|
+
constructor(media, fragmentTracker, hls) {
|
30860
30848
|
super('gap-controller', hls.logger);
|
30861
|
-
this.config = undefined;
|
30862
30849
|
this.media = null;
|
30863
|
-
this.fragmentTracker =
|
30864
|
-
this.hls =
|
30850
|
+
this.fragmentTracker = null;
|
30851
|
+
this.hls = null;
|
30865
30852
|
this.nudgeRetry = 0;
|
30866
30853
|
this.stallReported = false;
|
30867
30854
|
this.stalled = null;
|
30868
30855
|
this.moved = false;
|
30869
30856
|
this.seeking = false;
|
30870
30857
|
this.ended = 0;
|
30871
|
-
this.
|
30858
|
+
this.waiting = 0;
|
30872
30859
|
this.media = media;
|
30873
30860
|
this.fragmentTracker = fragmentTracker;
|
30874
30861
|
this.hls = hls;
|
30875
30862
|
}
|
30876
30863
|
destroy() {
|
30877
|
-
this.media = null;
|
30878
|
-
// @ts-ignore
|
30879
|
-
this.hls = this.fragmentTracker = null;
|
30864
|
+
this.media = this.hls = this.fragmentTracker = null;
|
30880
30865
|
}
|
30881
30866
|
|
30882
30867
|
/**
|
@@ -30886,12 +30871,12 @@ class GapController extends Logger {
|
|
30886
30871
|
* @param lastCurrentTime - Previously read playhead position
|
30887
30872
|
*/
|
30888
30873
|
poll(lastCurrentTime, activeFrag, levelDetails, state) {
|
30874
|
+
var _this$hls;
|
30889
30875
|
const {
|
30890
|
-
config,
|
30891
30876
|
media,
|
30892
30877
|
stalled
|
30893
30878
|
} = this;
|
30894
|
-
if (media
|
30879
|
+
if (!media) {
|
30895
30880
|
return;
|
30896
30881
|
}
|
30897
30882
|
const {
|
@@ -30911,43 +30896,45 @@ class GapController extends Logger {
|
|
30911
30896
|
if (!seeking) {
|
30912
30897
|
this.nudgeRetry = 0;
|
30913
30898
|
}
|
30914
|
-
if (
|
30915
|
-
|
30916
|
-
if (this.stallReported) {
|
30917
|
-
const _stalledDuration = self.performance.now() - stalled;
|
30918
|
-
this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
30919
|
-
this.stallReported = false;
|
30920
|
-
}
|
30921
|
-
this.stalled = null;
|
30899
|
+
if (this.waiting === 0) {
|
30900
|
+
this.stallResolved(currentTime);
|
30922
30901
|
}
|
30923
30902
|
return;
|
30924
30903
|
}
|
30925
30904
|
|
30926
30905
|
// Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek
|
30927
30906
|
if (beginSeek || seeked) {
|
30928
|
-
|
30907
|
+
if (seeked) {
|
30908
|
+
this.stallResolved(currentTime);
|
30909
|
+
}
|
30929
30910
|
return;
|
30930
30911
|
}
|
30931
30912
|
|
30932
30913
|
// The playhead should not be moving
|
30933
|
-
if (media.paused && !seeking || media.ended || media.playbackRate === 0
|
30914
|
+
if (media.paused && !seeking || media.ended || media.playbackRate === 0) {
|
30915
|
+
this.nudgeRetry = 0;
|
30916
|
+
this.stallResolved(currentTime);
|
30934
30917
|
// Fire MEDIA_ENDED to workaround event not being dispatched by browser
|
30935
|
-
if (!this.ended && media.ended) {
|
30918
|
+
if (!this.ended && media.ended && this.hls) {
|
30936
30919
|
this.ended = currentTime || 1;
|
30937
30920
|
this.hls.trigger(Events.MEDIA_ENDED, {
|
30938
30921
|
stalled: false
|
30939
30922
|
});
|
30940
30923
|
}
|
30924
|
+
return;
|
30925
|
+
}
|
30926
|
+
if (!BufferHelper.getBuffered(media).length) {
|
30941
30927
|
this.nudgeRetry = 0;
|
30942
30928
|
return;
|
30943
30929
|
}
|
30944
30930
|
const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
|
30945
30931
|
const nextStart = bufferInfo.nextStart || 0;
|
30946
|
-
|
30932
|
+
const fragmentTracker = this.fragmentTracker;
|
30933
|
+
if (seeking && fragmentTracker) {
|
30947
30934
|
// Waiting for seeking in a buffered range to complete
|
30948
30935
|
const hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
|
30949
30936
|
// Next buffered range is too far ahead to jump to while still seeking
|
30950
|
-
const noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !
|
30937
|
+
const noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !fragmentTracker.getPartialFragment(currentTime);
|
30951
30938
|
if (hasEnoughBuffer || noBufferGap) {
|
30952
30939
|
return;
|
30953
30940
|
}
|
@@ -30957,7 +30944,7 @@ class GapController extends Logger {
|
|
30957
30944
|
|
30958
30945
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
30959
30946
|
// The addition poll gives the browser a chance to jump the gap for us
|
30960
|
-
if (!this.moved && this.stalled !== null) {
|
30947
|
+
if (!this.moved && this.stalled !== null && fragmentTracker) {
|
30961
30948
|
// There is no playable buffer (seeked, waiting for buffer)
|
30962
30949
|
const isBuffered = bufferInfo.len > 0;
|
30963
30950
|
if (!isBuffered && !nextStart) {
|
@@ -30971,7 +30958,7 @@ class GapController extends Logger {
|
|
30971
30958
|
// that begins over 1 target duration after the video start position.
|
30972
30959
|
const isLive = !!(levelDetails != null && levelDetails.live);
|
30973
30960
|
const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
|
30974
|
-
const partialOrGap =
|
30961
|
+
const partialOrGap = fragmentTracker.getPartialFragment(currentTime);
|
30975
30962
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
30976
30963
|
if (!media.paused) {
|
30977
30964
|
this._trySkipBufferHole(partialOrGap);
|
@@ -30981,16 +30968,27 @@ class GapController extends Logger {
|
|
30981
30968
|
}
|
30982
30969
|
|
30983
30970
|
// Start tracking stall time
|
30971
|
+
const config = (_this$hls = this.hls) == null ? undefined : _this$hls.config;
|
30972
|
+
if (!config) {
|
30973
|
+
return;
|
30974
|
+
}
|
30975
|
+
const detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs;
|
30984
30976
|
const tnow = self.performance.now();
|
30977
|
+
const tWaiting = this.waiting;
|
30985
30978
|
if (stalled === null) {
|
30986
|
-
|
30979
|
+
// Use time of recent "waiting" event
|
30980
|
+
if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) {
|
30981
|
+
this.stalled = tWaiting;
|
30982
|
+
} else {
|
30983
|
+
this.stalled = tnow;
|
30984
|
+
}
|
30987
30985
|
return;
|
30988
30986
|
}
|
30989
30987
|
const stalledDuration = tnow - stalled;
|
30990
|
-
if (!seeking && stalledDuration >=
|
30988
|
+
if (!seeking && (stalledDuration >= detectStallWithCurrentTimeMs || tWaiting) && this.hls) {
|
30991
30989
|
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
30992
30990
|
if (state === State.ENDED && !(levelDetails != null && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? undefined : levelDetails.edge) || 0)) < 1) {
|
30993
|
-
if (
|
30991
|
+
if (this.ended) {
|
30994
30992
|
return;
|
30995
30993
|
}
|
30996
30994
|
this.ended = currentTime || 1;
|
@@ -31001,13 +30999,27 @@ class GapController extends Logger {
|
|
31001
30999
|
}
|
31002
31000
|
// Report stalling after trying to fix
|
31003
31001
|
this._reportStall(bufferInfo);
|
31004
|
-
if (!this.media) {
|
31002
|
+
if (!this.media || !this.hls) {
|
31005
31003
|
return;
|
31006
31004
|
}
|
31007
31005
|
}
|
31008
31006
|
const bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole);
|
31009
31007
|
this._tryFixBufferStall(bufferedWithHoles, stalledDuration);
|
31010
31008
|
}
|
31009
|
+
stallResolved(currentTime) {
|
31010
|
+
const stalled = this.stalled;
|
31011
|
+
if (stalled && this.hls) {
|
31012
|
+
this.stalled = null;
|
31013
|
+
// The playhead is now moving, but was previously stalled
|
31014
|
+
if (this.stallReported) {
|
31015
|
+
const stalledDuration = self.performance.now() - stalled;
|
31016
|
+
this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(stalledDuration)}ms`);
|
31017
|
+
this.stallReported = false;
|
31018
|
+
this.waiting = 0;
|
31019
|
+
this.hls.trigger(Events.STALL_RESOLVED, {});
|
31020
|
+
}
|
31021
|
+
}
|
31022
|
+
}
|
31011
31023
|
|
31012
31024
|
/**
|
31013
31025
|
* Detects and attempts to fix known buffer stalling issues.
|
@@ -31016,12 +31028,13 @@ class GapController extends Logger {
|
|
31016
31028
|
* @private
|
31017
31029
|
*/
|
31018
31030
|
_tryFixBufferStall(bufferInfo, stalledDurationMs) {
|
31031
|
+
var _this$hls2;
|
31019
31032
|
const {
|
31020
|
-
config,
|
31021
31033
|
fragmentTracker,
|
31022
31034
|
media
|
31023
31035
|
} = this;
|
31024
|
-
|
31036
|
+
const config = (_this$hls2 = this.hls) == null ? undefined : _this$hls2.config;
|
31037
|
+
if (!media || !fragmentTracker || !config) {
|
31025
31038
|
return;
|
31026
31039
|
}
|
31027
31040
|
const currentTime = media.currentTime;
|
@@ -31041,13 +31054,12 @@ class GapController extends Logger {
|
|
31041
31054
|
// we may just have to "nudge" the playlist as the browser decoding/rendering engine
|
31042
31055
|
// needs to cross some sort of threshold covering all source-buffers content
|
31043
31056
|
// to start playing properly.
|
31044
|
-
|
31057
|
+
const bufferedRanges = bufferInfo.buffered;
|
31058
|
+
if ((bufferedRanges && bufferedRanges.length > 1 && bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
31045
31059
|
this.warn('Trying to nudge playhead over buffer-hole');
|
31046
31060
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
31047
31061
|
// We only try to jump the hole if it's under the configured size
|
31048
|
-
|
31049
|
-
this.stalled = null;
|
31050
|
-
this._tryNudgeBuffer();
|
31062
|
+
this._tryNudgeBuffer(bufferInfo);
|
31051
31063
|
}
|
31052
31064
|
}
|
31053
31065
|
|
@@ -31060,9 +31072,10 @@ class GapController extends Logger {
|
|
31060
31072
|
const {
|
31061
31073
|
hls,
|
31062
31074
|
media,
|
31063
|
-
stallReported
|
31075
|
+
stallReported,
|
31076
|
+
stalled
|
31064
31077
|
} = this;
|
31065
|
-
if (!stallReported && media) {
|
31078
|
+
if (!stallReported && stalled !== null && media && hls) {
|
31066
31079
|
// Report stalled error once
|
31067
31080
|
this.stallReported = true;
|
31068
31081
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
@@ -31072,7 +31085,11 @@ class GapController extends Logger {
|
|
31072
31085
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
31073
31086
|
fatal: false,
|
31074
31087
|
error,
|
31075
|
-
buffer: bufferInfo.len
|
31088
|
+
buffer: bufferInfo.len,
|
31089
|
+
bufferInfo,
|
31090
|
+
stalled: {
|
31091
|
+
start: stalled
|
31092
|
+
}
|
31076
31093
|
});
|
31077
31094
|
}
|
31078
31095
|
}
|
@@ -31083,12 +31100,13 @@ class GapController extends Logger {
|
|
31083
31100
|
* @private
|
31084
31101
|
*/
|
31085
31102
|
_trySkipBufferHole(partial) {
|
31103
|
+
var _this$hls3;
|
31086
31104
|
const {
|
31087
|
-
|
31088
|
-
hls,
|
31105
|
+
fragmentTracker,
|
31089
31106
|
media
|
31090
31107
|
} = this;
|
31091
|
-
|
31108
|
+
const config = (_this$hls3 = this.hls) == null ? undefined : _this$hls3.config;
|
31109
|
+
if (!media || !fragmentTracker || !config) {
|
31092
31110
|
return 0;
|
31093
31111
|
}
|
31094
31112
|
|
@@ -31103,9 +31121,6 @@ class GapController extends Logger {
|
|
31103
31121
|
if (gapLength > 0 && (bufferStarved || waiting)) {
|
31104
31122
|
// Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial
|
31105
31123
|
if (gapLength > config.maxBufferHole) {
|
31106
|
-
const {
|
31107
|
-
fragmentTracker
|
31108
|
-
} = this;
|
31109
31124
|
let startGap = false;
|
31110
31125
|
if (currentTime === 0) {
|
31111
31126
|
const startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN);
|
@@ -31136,17 +31151,18 @@ class GapController extends Logger {
|
|
31136
31151
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
31137
31152
|
this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
31138
31153
|
this.moved = true;
|
31139
|
-
this.stalled = null;
|
31140
31154
|
media.currentTime = targetTime;
|
31141
|
-
if (partial && !partial.gap) {
|
31155
|
+
if (partial && !partial.gap && this.hls) {
|
31142
31156
|
const error = new Error(`fragment loaded with buffer holes, seeking from ${currentTime} to ${targetTime}`);
|
31143
|
-
hls.trigger(Events.ERROR, {
|
31157
|
+
this.hls.trigger(Events.ERROR, {
|
31144
31158
|
type: ErrorTypes.MEDIA_ERROR,
|
31145
31159
|
details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
|
31146
31160
|
fatal: false,
|
31147
31161
|
error,
|
31148
31162
|
reason: error.message,
|
31149
|
-
frag: partial
|
31163
|
+
frag: partial,
|
31164
|
+
buffer: bufferInfo.len,
|
31165
|
+
bufferInfo
|
31150
31166
|
});
|
31151
31167
|
}
|
31152
31168
|
return targetTime;
|
@@ -31159,15 +31175,15 @@ class GapController extends Logger {
|
|
31159
31175
|
* Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount.
|
31160
31176
|
* @private
|
31161
31177
|
*/
|
31162
|
-
_tryNudgeBuffer() {
|
31178
|
+
_tryNudgeBuffer(bufferInfo) {
|
31163
31179
|
const {
|
31164
|
-
config,
|
31165
31180
|
hls,
|
31166
31181
|
media,
|
31167
31182
|
nudgeRetry
|
31168
31183
|
} = this;
|
31169
|
-
|
31170
|
-
|
31184
|
+
const config = hls == null ? undefined : hls.config;
|
31185
|
+
if (!media || !config) {
|
31186
|
+
return 0;
|
31171
31187
|
}
|
31172
31188
|
const currentTime = media.currentTime;
|
31173
31189
|
this.nudgeRetry++;
|
@@ -31181,7 +31197,9 @@ class GapController extends Logger {
|
|
31181
31197
|
type: ErrorTypes.MEDIA_ERROR,
|
31182
31198
|
details: ErrorDetails.BUFFER_NUDGE_ON_STALL,
|
31183
31199
|
error,
|
31184
|
-
fatal: false
|
31200
|
+
fatal: false,
|
31201
|
+
buffer: bufferInfo.len,
|
31202
|
+
bufferInfo
|
31185
31203
|
});
|
31186
31204
|
} else {
|
31187
31205
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
@@ -31190,7 +31208,9 @@ class GapController extends Logger {
|
|
31190
31208
|
type: ErrorTypes.MEDIA_ERROR,
|
31191
31209
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
31192
31210
|
error,
|
31193
|
-
fatal: true
|
31211
|
+
fatal: true,
|
31212
|
+
buffer: bufferInfo.len,
|
31213
|
+
bufferInfo
|
31194
31214
|
});
|
31195
31215
|
}
|
31196
31216
|
}
|
@@ -31241,11 +31261,18 @@ class StreamController extends BaseStreamController {
|
|
31241
31261
|
this.backtrackFragment = null;
|
31242
31262
|
this.audioCodecSwitch = false;
|
31243
31263
|
this.videoBuffer = null;
|
31264
|
+
this.onMediaWaiting = () => {
|
31265
|
+
const gapController = this.gapController;
|
31266
|
+
if (gapController) {
|
31267
|
+
gapController.waiting = self.performance.now();
|
31268
|
+
}
|
31269
|
+
};
|
31244
31270
|
this.onMediaPlaying = () => {
|
31245
31271
|
// tick to speed up FRAG_CHANGED triggering
|
31246
31272
|
const gapController = this.gapController;
|
31247
31273
|
if (gapController) {
|
31248
31274
|
gapController.ended = 0;
|
31275
|
+
gapController.waiting = 0;
|
31249
31276
|
}
|
31250
31277
|
this.tick();
|
31251
31278
|
};
|
@@ -31301,7 +31328,7 @@ class StreamController extends BaseStreamController {
|
|
31301
31328
|
}
|
31302
31329
|
onHandlerDestroying() {
|
31303
31330
|
// @ts-ignore
|
31304
|
-
this.onMediaPlaying = this.onMediaSeeked = null;
|
31331
|
+
this.onMediaPlaying = this.onMediaSeeked = this.onMediaWaiting = null;
|
31305
31332
|
this.unregisterListeners();
|
31306
31333
|
super.onHandlerDestroying();
|
31307
31334
|
}
|
@@ -31632,9 +31659,11 @@ class StreamController extends BaseStreamController {
|
|
31632
31659
|
const media = data.media;
|
31633
31660
|
media.removeEventListener('playing', this.onMediaPlaying);
|
31634
31661
|
media.removeEventListener('seeked', this.onMediaSeeked);
|
31662
|
+
media.removeEventListener('waiting', this.onMediaWaiting);
|
31635
31663
|
media.addEventListener('playing', this.onMediaPlaying);
|
31636
31664
|
media.addEventListener('seeked', this.onMediaSeeked);
|
31637
|
-
|
31665
|
+
media.addEventListener('waiting', this.onMediaWaiting);
|
31666
|
+
this.gapController = new GapController(media, this.fragmentTracker, this.hls);
|
31638
31667
|
}
|
31639
31668
|
onMediaDetaching(event, data) {
|
31640
31669
|
const {
|
@@ -31643,6 +31672,7 @@ class StreamController extends BaseStreamController {
|
|
31643
31672
|
if (media) {
|
31644
31673
|
media.removeEventListener('playing', this.onMediaPlaying);
|
31645
31674
|
media.removeEventListener('seeked', this.onMediaSeeked);
|
31675
|
+
media.removeEventListener('waiting', this.onMediaWaiting);
|
31646
31676
|
}
|
31647
31677
|
this.videoBuffer = null;
|
31648
31678
|
this.fragPlaying = null;
|
@@ -32070,7 +32100,7 @@ class StreamController extends BaseStreamController {
|
|
32070
32100
|
let startPosition = this.startPosition;
|
32071
32101
|
// only adjust currentTime if different from startPosition or if startPosition not buffered
|
32072
32102
|
// at that stage, there should be only one buffered range, as we reach that code after first fragment has been buffered
|
32073
|
-
if (startPosition >= 0) {
|
32103
|
+
if (startPosition >= 0 && currentTime < startPosition) {
|
32074
32104
|
if (media.seeking) {
|
32075
32105
|
this.log(`could not seek to ${startPosition}, already seeking at ${currentTime}`);
|
32076
32106
|
return;
|