Skip to content

Commit 1b921b8

Browse files
committed
Make nextLevelSwitch more responsive and accurate
1 parent 52d3afc commit 1b921b8

File tree

2 files changed

+41
-36
lines changed

2 files changed

+41
-36
lines changed

src/controller/abr-controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,7 @@ class AbrController extends Logger implements AbrComponentAPI {
942942
ttfbEstimateSec,
943943
adjustedbw,
944944
bitrate * avgDuration,
945-
levelDetails === undefined,
945+
!levelDetails || levelDetails.live,
946946
);
947947

948948
const canSwitchWithinTolerance =

src/controller/stream-controller.ts

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ export default class StreamController
7070
private altAudio: AlternateAudio = AlternateAudio.DISABLED;
7171
private audioOnly: boolean = false;
7272
private fragPlaying: Fragment | null = null;
73-
private fragLastKbps: number = 0;
7473
private couldBacktrack: boolean = false;
7574
private backtrackFragment: Fragment | null = null;
7675
private audioCodecSwitch: boolean = false;
@@ -436,43 +435,46 @@ export default class StreamController
436435
* we should take into account new segment fetch time
437436
*/
438437
public nextLevelSwitch() {
439-
const { levels, media } = this;
438+
const { levels, media, hls, config } = this;
440439
// ensure that media is defined and that metadata are available (to retrieve currentTime)
441-
if (media?.readyState) {
442-
let fetchdelay;
443-
const fragPlayingCurrent = this.getAppendedFrag(media.currentTime);
444-
if (fragPlayingCurrent && fragPlayingCurrent.start > 1) {
445-
// flush buffer preceding current fragment (flush until current fragment start offset)
446-
// minus 1s to avoid video freezing, that could happen if we flush keyframe of current video ...
447-
this.flushMainBuffer(0, fragPlayingCurrent.start - 1);
440+
if (media?.readyState && levels && hls && config) {
441+
const bufferInfo = this.getMainFwdBufferInfo();
442+
if (!bufferInfo) {
443+
return;
448444
}
449445
const levelDetails = this.getLevelDetails();
450-
if (levelDetails?.live) {
451-
const bufferInfo = this.getMainFwdBufferInfo();
452-
// Do not flush in live stream with low buffer
453-
if (!bufferInfo || bufferInfo.len < levelDetails.targetduration * 2) {
454-
return;
455-
}
456-
}
457-
if (!media.paused && levels) {
446+
447+
let fetchdelay = 0;
448+
if (!media.paused) {
458449
// add a safety delay of 1s
459-
const nextLevelId = this.hls.nextLoadLevel;
450+
const ttfbSec = 1 + hls.ttfbEstimate / 1000;
451+
const bandwidth = hls.bandwidthEstimate * config.abrBandWidthUpFactor;
452+
const nextLevelId = hls.nextLoadLevel;
460453
const nextLevel = levels[nextLevelId];
461-
const fragLastKbps = this.fragLastKbps;
462-
if (fragLastKbps && this.fragCurrent) {
463-
fetchdelay =
464-
(this.fragCurrent.duration * nextLevel.maxBitrate) /
465-
(1000 * fragLastKbps) +
466-
1;
467-
} else {
468-
fetchdelay = 0;
454+
const fragDuration =
455+
(levelDetails &&
456+
(this.loadingParts
457+
? levelDetails.partTarget
458+
: levelDetails.averagetargetduration)) ||
459+
this.fragCurrent?.duration ||
460+
6;
461+
fetchdelay =
462+
ttfbSec + (nextLevel.maxBitrate * fragDuration) / bandwidth;
463+
if (!nextLevel.details) {
464+
fetchdelay += ttfbSec;
469465
}
470-
} else {
471-
fetchdelay = 0;
472466
}
473-
// this.log('fetchdelay:'+fetchdelay);
467+
468+
// Do not flush in live stream with low buffer
469+
470+
const okToFlushForwardBuffer =
471+
!levelDetails?.live ||
472+
(bufferInfo.len || 0) > levelDetails.targetduration * 2;
473+
474474
// find buffer range that will be reached once new fragment will be fetched
475-
const bufferedFrag = this.getBufferedFrag(media.currentTime + fetchdelay);
475+
const bufferedFrag = okToFlushForwardBuffer
476+
? this.getBufferedFrag(media.currentTime + fetchdelay)
477+
: null;
476478
if (bufferedFrag) {
477479
// we can flush buffer range following this one without stalling playback
478480
const nextBufferedFrag = this.followingBufferedFrag(bufferedFrag);
@@ -498,7 +500,15 @@ export default class StreamController
498500
this.flushMainBuffer(startPts, Number.POSITIVE_INFINITY);
499501
}
500502
}
503+
// remove back-buffer
504+
const fragPlayingCurrent = this.getAppendedFrag(media.currentTime);
505+
if (fragPlayingCurrent && fragPlayingCurrent.start > 1) {
506+
// flush buffer preceding current fragment (flush until current fragment start offset)
507+
// minus 1s to avoid video freezing, that could happen if we flush keyframe of current video ...
508+
this.flushMainBuffer(0, fragPlayingCurrent.start - 1);
509+
}
501510
}
511+
this.tickImmediate();
502512
}
503513

504514
private abortCurrentFrag() {
@@ -601,7 +611,6 @@ export default class StreamController
601611
this.log('Trigger BUFFER_RESET');
602612
this.hls.trigger(Events.BUFFER_RESET, undefined);
603613
this.couldBacktrack = false;
604-
this.fragLastKbps = 0;
605614
this.fragPlaying = this.backtrackFragment = null;
606615
this.altAudio = AlternateAudio.DISABLED;
607616
this.audioOnly = false;
@@ -987,10 +996,6 @@ export default class StreamController
987996
}
988997
return;
989998
}
990-
const stats = part ? part.stats : frag.stats;
991-
this.fragLastKbps = Math.round(
992-
(8 * stats.total) / (stats.buffering.end - stats.loading.first),
993-
);
994999
if (isMediaFragment(frag)) {
9951000
this.fragPrevious = frag;
9961001
}

0 commit comments

Comments
 (0)