Skip to content

Commit 1596a02

Browse files
committed
Limit buffering while paused outside live sliding window (#7788)
* Limit buffering while paused outside live sliding window and better handle QuotaExceededError Improve flushing behavior on next and immediate switch when playhead is behind live window Fixes #7777 * Remove buffered audio on full quality switch when playhead is behind live sliding start to keep buffered media aligned
1 parent 7c9e8f7 commit 1596a02

4 files changed

Lines changed: 85 additions & 18 deletions

File tree

api-extractor/report/hls.js.api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,8 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP
404404
// (undocumented)
405405
protected doTick(): void;
406406
// (undocumented)
407+
protected exceedsMaxBuffer(bufferInfo: BufferInfo, maxBufLen: number, selected: Fragment): boolean;
408+
// (undocumented)
407409
protected filterReplacedPrimary(frag: MediaFragment | null, details: LevelDetails | undefined): MediaFragment | null;
408410
// (undocumented)
409411
protected flushBufferGap(frag: Fragment): void;

src/controller/audio-stream-controller.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,10 @@ class AudioStreamController
413413
return;
414414
}
415415

416+
if (this.exceedsMaxBuffer(bufferInfo, maxBufLen, frag)) {
417+
return;
418+
}
419+
416420
// Request audio segments up to one fragment ahead of main stream-controller
417421
let mainFragLoading = this.mainFragLoading?.frag || null;
418422
if (

src/controller/base-stream-controller.ts

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,28 @@ export default class BaseStreamController
12951295
return Math.min(maxBufLen, config.maxMaxBufferLength);
12961296
}
12971297

1298+
protected exceedsMaxBuffer(
1299+
bufferInfo: BufferInfo,
1300+
maxBufLen: number,
1301+
selected: Fragment,
1302+
): boolean {
1303+
const nextStart = bufferInfo.nextStart;
1304+
if (nextStart && selected.start > nextStart) {
1305+
const bufferedRanges = bufferInfo.buffered;
1306+
if (bufferedRanges) {
1307+
let fullBufferLength = bufferInfo.len;
1308+
const bufferedIndex = bufferInfo.bufferedIndex;
1309+
for (let i = bufferedRanges.length - 1; i > bufferedIndex; i--) {
1310+
if (bufferedRanges[i].start < selected.start) {
1311+
fullBufferLength += bufferedRanges[i].end - bufferedRanges[i].start;
1312+
}
1313+
}
1314+
return fullBufferLength >= maxBufLen;
1315+
}
1316+
}
1317+
return false;
1318+
}
1319+
12981320
protected reduceMaxBufferLength(threshold: number, fragDuration: number) {
12991321
const config = this.config;
13001322
const minLength = Math.max(
@@ -1341,7 +1363,7 @@ export default class BaseStreamController
13411363

13421364
// find fragment index, contiguous with end of buffer position
13431365
const { config } = this;
1344-
const start = fragments[0].start;
1366+
const playlistStart = levelDetails.fragmentStart;
13451367
const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
13461368
let frag: MediaFragment | null = null;
13471369

@@ -1361,26 +1383,49 @@ export default class BaseStreamController
13611383
(!levelDetails.PTSKnown &&
13621384
!this.startFragRequested &&
13631385
this.startPosition === -1) ||
1364-
pos < start
1386+
pos < playlistStart
13651387
) {
13661388
if (canLoadParts && !this.loadingParts) {
13671389
this.log(`LL-Part loading ON for initial live fragment`);
13681390
this.loadingParts = true;
13691391
}
13701392
frag = this.getInitialLiveFragment(levelDetails);
1393+
const configValue = this.config.startPosition;
13711394
const mainStart = this.hls.startPosition;
13721395
const liveSyncPosition = this.hls.liveSyncPosition;
1373-
const startPosition = frag
1374-
? (mainStart !== -1 && mainStart >= start
1375-
? mainStart
1376-
: liveSyncPosition) || frag.start
1377-
: pos;
1378-
this.log(
1379-
`Setting startPosition to ${startPosition} to match start frag at live edge. mainStart: ${mainStart} liveSyncPosition: ${liveSyncPosition} frag.start: ${frag?.start}`,
1380-
);
1381-
this.startPosition = this.nextLoadPosition = startPosition;
1396+
const fragStart = frag?.start || 0;
1397+
1398+
let startPosition: number | undefined;
1399+
let reason: string | undefined;
1400+
if (mainStart !== -1 && mainStart >= playlistStart) {
1401+
startPosition = mainStart;
1402+
reason = mainStart === configValue ? 'config' : 'next load start';
1403+
} else if (liveSyncPosition !== null) {
1404+
startPosition = liveSyncPosition;
1405+
reason = 'live edge';
1406+
} else {
1407+
startPosition = pos;
1408+
reason = 'buffer pos';
1409+
}
1410+
if (startPosition < fragStart) {
1411+
startPosition = fragStart;
1412+
reason = 'live frag start';
1413+
}
1414+
if (startPosition < playlistStart) {
1415+
startPosition = playlistStart;
1416+
reason = 'playlist start';
1417+
}
1418+
if (
1419+
this.startPosition != startPosition ||
1420+
this.nextLoadPosition != startPosition
1421+
) {
1422+
this.log(
1423+
`Setting startPosition to ${startPosition.toFixed(3)} ${mainStart === -1 ? '' : `(from ${configValue}) `}based on ${reason}. live edge: ${liveSyncPosition} live frag start: ${fragStart.toFixed(3)} playlist start: ${playlistStart.toFixed(3)} buffer pos: ${pos}`,
1424+
);
1425+
this.startPosition = this.nextLoadPosition = startPosition;
1426+
}
13821427
}
1383-
} else if (pos <= start) {
1428+
} else if (pos <= playlistStart) {
13841429
// VoD playlist: if loadPosition before start of playlist, load first fragment
13851430
frag = fragments[0];
13861431
}
@@ -1404,11 +1449,13 @@ export default class BaseStreamController
14041449
}
14051450

14061451
protected isLoopLoading(frag: Fragment, targetBufferTime: number): boolean {
1452+
if (this.nextLoadPosition <= targetBufferTime) {
1453+
return false;
1454+
}
14071455
const trackerState = this.fragmentTracker.getState(frag);
14081456
return (
1409-
(trackerState === FragmentState.OK ||
1410-
(trackerState === FragmentState.PARTIAL && !!frag.gap)) &&
1411-
this.nextLoadPosition > targetBufferTime
1457+
trackerState === FragmentState.OK ||
1458+
(trackerState === FragmentState.PARTIAL && !!frag.gap)
14121459
);
14131460
}
14141461

@@ -1596,14 +1643,16 @@ export default class BaseStreamController
15961643
if (fragPrevious) {
15971644
if (levelDetails.hasProgramDateTime) {
15981645
// Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding
1599-
this.log(
1600-
`Live playlist, switching playlist, load frag with same PDT: ${fragPrevious.programDateTime}`,
1601-
);
16021646
frag = findFragmentByPDT(
16031647
fragments,
16041648
fragPrevious.endProgramDateTime,
16051649
this.config.maxFragLookUpTolerance,
16061650
);
1651+
if (frag) {
1652+
this.log(
1653+
`Live playlist, switching playlist, load frag with same PDT: ${fragPrevious.programDateTime}`,
1654+
);
1655+
}
16071656
}
16081657
if (!frag) {
16091658
// SN does not need to be accurate between renditions, but depending on the packaging it may be so.

src/controller/stream-controller.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,11 @@ export default class StreamController
370370
if (!frag) {
371371
return;
372372
}
373+
374+
if (this.exceedsMaxBuffer(bufferInfo, maxBufLen, frag)) {
375+
return;
376+
}
377+
373378
if (frag.initSegment && !frag.initSegment.data && !this.bitrateTest) {
374379
frag = frag.initSegment;
375380
}
@@ -427,6 +432,13 @@ export default class StreamController
427432
public immediateLevelSwitch() {
428433
this.abortCurrentFrag();
429434
this.flushMainBuffer(0, Number.POSITIVE_INFINITY);
435+
if (this.altAudio !== AlternateAudio.DISABLED) {
436+
// If behind the live window, remove audio too to keep buffered media aligned
437+
const liveWindowStart = this.getLevelDetails()?.fragmentStart || 0;
438+
if (liveWindowStart > this.lastCurrentTime) {
439+
super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');
440+
}
441+
}
430442
}
431443

432444
/**

0 commit comments

Comments
 (0)