Skip to content

Commit 4dcb0fb

Browse files
committed
Manage interstitial stalls and improve jagged discontinuity fragment selection at interstitial boundaries
1 parent e4b2e75 commit 4dcb0fb

File tree

4 files changed

+80
-24
lines changed

4 files changed

+80
-24
lines changed

src/controller/abr-controller.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class AbrController extends Logger implements AbrComponentAPI {
4646
private fragCurrent: Fragment | null = null;
4747
private partCurrent: Part | null = null;
4848
private bitrateTestDelay: number = 0;
49+
private rebufferNotice: number = -1;
4950

5051
public bwEstimator: EwmaBandWidthEstimator;
5152

@@ -647,6 +648,7 @@ class AbrController extends Logger implements AbrComponentAPI {
647648
bwUpFactor,
648649
);
649650
if (bestLevel >= 0) {
651+
this.rebufferNotice = -1;
650652
return bestLevel;
651653
}
652654
}
@@ -688,11 +690,14 @@ class AbrController extends Logger implements AbrComponentAPI {
688690
bwFactor,
689691
bwUpFactor,
690692
);
691-
this.info(
692-
`${
693-
bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'
694-
}, optimal quality level ${bestLevel}`,
695-
);
693+
if (this.rebufferNotice !== bestLevel) {
694+
this.rebufferNotice = bestLevel;
695+
this.info(
696+
`${
697+
bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'
698+
}, optimal quality level ${bestLevel}`,
699+
);
700+
}
696701
if (bestLevel > -1) {
697702
return bestLevel;
698703
}

src/controller/base-stream-controller.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1310,7 +1310,14 @@ export default class BaseStreamController
13101310
: levelDetails.fragmentEnd;
13111311
frag = this.getFragmentAtPosition(pos, end, levelDetails);
13121312
}
1313-
const programFrag = this.filterReplacedPrimary(frag, levelDetails);
1313+
let programFrag = this.filterReplacedPrimary(frag, levelDetails);
1314+
if (!programFrag && frag) {
1315+
const curSNIdx = frag.sn - levelDetails.startSN;
1316+
programFrag = this.filterReplacedPrimary(
1317+
fragments[curSNIdx + 1] || null,
1318+
levelDetails,
1319+
);
1320+
}
13141321
return this.mapToInitFragWhenRequired(programFrag);
13151322
}
13161323

src/controller/interstitials-controller.ts

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,7 +1715,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
17151715
}
17161716

17171717
// Schedule buffer control
1718-
private checkBuffer() {
1718+
private checkBuffer(starved?: boolean) {
17191719
const items = this.schedule.items;
17201720
if (!items) {
17211721
return;
@@ -1726,8 +1726,11 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
17261726
this.timelinePos,
17271727
0,
17281728
);
1729-
1730-
this.updateBufferedPos(bufferInfo.end, items, bufferInfo.len === 0);
1729+
if (starved) {
1730+
this.bufferedPos = this.timelinePos;
1731+
}
1732+
starved ||= bufferInfo.len < 1;
1733+
this.updateBufferedPos(bufferInfo.end, items, starved);
17311734
}
17321735

17331736
private updateBufferedPos(
@@ -1788,10 +1791,13 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
17881791
} else if (
17891792
bufferIsEmpty &&
17901793
playingItem &&
1791-
!this.itemsMatch(playingItem, bufferingItem) &&
1792-
bufferEndIndex === playingIndex
1794+
!this.itemsMatch(playingItem, bufferingItem)
17931795
) {
1794-
this.bufferedToItem(playingItem);
1796+
if (bufferEndIndex === playingIndex) {
1797+
this.bufferedToItem(playingItem);
1798+
} else if (bufferEndIndex === playingIndex + 1) {
1799+
this.bufferedToItem(items[bufferEndIndex]);
1800+
}
17951801
}
17961802
}
17971803

@@ -1930,7 +1936,13 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
19301936
if (neverLoaded) {
19311937
const timelineStart = interstitial.timelineStart;
19321938
if (interstitial.appendInPlace) {
1933-
this.flushFrontBuffer(timelineStart + 0.25);
1939+
const playingItem = this.playingItem;
1940+
if (
1941+
!this.isInterstitial(playingItem) &&
1942+
playingItem?.nextEvent?.identifier === interstitial.identifier
1943+
) {
1944+
this.flushFrontBuffer(timelineStart + 0.25);
1945+
}
19341946
}
19351947
let hlsStartOffset;
19361948
let liveStartPosition = 0;
@@ -1998,6 +2010,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
19982010
if (!requiredTracks) {
19992011
return;
20002012
}
2013+
this.log(`Removing front buffer starting at ${startOffset}`);
20012014
const sourceBufferNames = Object.keys(requiredTracks);
20022015
sourceBufferNames.forEach((type: SourceBufferName) => {
20032016
this.hls.trigger(Events.BUFFER_FLUSHING, {
@@ -2087,12 +2100,13 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
20872100
}
20882101
}
20892102
}
2103+
const assetId = assetItem.identifier;
20902104
const playerConfig: Partial<HlsConfig> = {
20912105
...userConfig,
20922106
autoStartLoad: true,
20932107
startFragPrefetch: true,
20942108
primarySessionId: primary.sessionId,
2095-
assetPlayerId: assetItem.identifier,
2109+
assetPlayerId: assetId,
20962110
abrEwmaDefaultEstimate: primary.bandwidthEstimate,
20972111
interstitialsController: undefined,
20982112
startPosition,
@@ -2114,6 +2128,11 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
21142128
contentId: hash(assetItem.uri),
21152129
});
21162130
}
2131+
if (this.getAssetPlayer(assetId)) {
2132+
this.warn(
2133+
`Duplicate date range identifier ${interstitial} and asset ${assetId}`,
2134+
);
2135+
}
21172136
const player = new HlsAssetPlayer(
21182137
this.HlsPlayerClass,
21192138
playerConfig,
@@ -2122,7 +2141,6 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
21222141
);
21232142
this.playerQueue.push(player);
21242143
interstitial.assetList[assetListIndex] = assetItem;
2125-
const assetId = assetItem.identifier;
21262144
// Listen for LevelDetails and PTS change to update duration
21272145
const updateAssetPlayerDetails = (details: LevelDetails) => {
21282146
if (details.live) {
@@ -2181,14 +2199,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
21812199
}
21822200
};
21832201
player.on(Events.BUFFER_CODECS, onBufferCodecs);
2184-
const bufferedToEnd = (name: Events.BUFFERED_TO_END) => {
2202+
const bufferedToEnd = () => {
21852203
const inQueuPlayer = this.getAssetPlayer(assetId);
21862204
this.log(`buffered to end of asset ${inQueuPlayer}`);
21872205
if (!inQueuPlayer) {
21882206
return;
21892207
}
2190-
inQueuPlayer.off(Events.BUFFERED_TO_END, bufferedToEnd);
2191-
21922208
// Preload at end of asset
21932209
const scheduleIndex = this.schedule.findEventIndex(
21942210
interstitial.identifier,
@@ -2202,7 +2218,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
22022218
!interstitial.isAssetPastPlayoutLimit(nextAssetIndex) &&
22032219
!interstitial.assetList[nextAssetIndex].error
22042220
) {
2205-
this.bufferedToItem(item, assetListIndex + 1);
2221+
this.bufferedToItem(item, nextAssetIndex);
22062222
} else {
22072223
const nextItem = this.schedule.items?.[scheduleIndex + 1];
22082224
if (nextItem) {
@@ -2228,6 +2244,30 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
22282244
player.once(Events.MEDIA_ENDED, endedWithAssetIndex(assetListIndex));
22292245
player.once(Events.PLAYOUT_LIMIT_REACHED, endedWithAssetIndex(Infinity));
22302246
player.on(Events.ERROR, (event: Events.ERROR, data: ErrorData) => {
2247+
const inQueuPlayer = this.getAssetPlayer(assetId);
2248+
if (data.details === ErrorDetails.BUFFER_STALLED_ERROR) {
2249+
if (inQueuPlayer?.media) {
2250+
const assetCurrentTime = inQueuPlayer.currentTime;
2251+
const distanceFromEnd = inQueuPlayer.duration - assetCurrentTime;
2252+
if (
2253+
assetCurrentTime &&
2254+
interstitial.appendInPlace &&
2255+
distanceFromEnd / inQueuPlayer.media.playbackRate < 0.5
2256+
) {
2257+
this.log(
2258+
`Advancing buffer past end of asset ${assetId} ${interstitial} at ${inQueuPlayer.media.currentTime}`,
2259+
);
2260+
bufferedToEnd();
2261+
} else {
2262+
this.warn(
2263+
`Stalled at ${assetCurrentTime} of ${assetCurrentTime + distanceFromEnd} in asset ${assetId} ${interstitial}`,
2264+
);
2265+
this.onTimeupdate();
2266+
this.checkBuffer(true);
2267+
}
2268+
}
2269+
return;
2270+
}
22312271
this.handleAssetItemError(
22322272
data,
22332273
interstitial,
@@ -2336,10 +2376,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
23362376
});
23372377
}
23382378

2339-
if (!player.bufferedInPlaceToEnd(media)) {
2340-
// detach media and attach to interstitial player if it does not have another element attached
2341-
this.bufferAssetPlayer(player, media);
2342-
}
2379+
// detach media and attach to interstitial player if it does not have another element attached
2380+
this.bufferAssetPlayer(player, media);
23432381
}
23442382

23452383
private bufferAssetPlayer(player: HlsAssetPlayer, media: HTMLMediaElement) {
@@ -2569,6 +2607,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`,
25692607
if (interstitial) {
25702608
this.primaryFallback(interstitial);
25712609
}
2610+
break;
2611+
}
2612+
case ErrorDetails.BUFFER_STALLED_ERROR: {
2613+
this.onTimeupdate();
2614+
this.checkBuffer(true);
2615+
break;
25722616
}
25732617
}
25742618
}

src/controller/interstitials-schedule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ export class InterstitialsSchedule extends Logger {
628628
return !playlists.some((playlistType) => {
629629
const details = mediaSelection[playlistType].details;
630630
const playlistEnd = details.edge;
631-
if (resumeTime > playlistEnd) {
631+
if (resumeTime >= playlistEnd) {
632632
// Live playback - resumption segments are not yet available
633633
this.log(
634634
`"${interstitial.identifier}" resumption ${resumeTime} past ${playlistType} playlist end ${playlistEnd}`,

0 commit comments

Comments
 (0)