Skip to content

Commit e55fecd

Browse files
authored
Allow primary segment prefetching for interstitial resumption and exp… (#7124)
* Allow primary segment prefetching for interstitial resumption and expand allowance of append-in-line strategy Resolves #7117 * Handle shortened asset-list result and improve asset-list loaded logging Fix prefetching asset at live start Resolves #7117 * Fix prefetching asset segments at target seek position
1 parent edcf39f commit e55fecd

13 files changed

+292
-138
lines changed

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

+13-3
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ export class AudioStreamController extends BaseStreamController implements Netwo
209209
// (undocumented)
210210
protected resetLoadingState(): void;
211211
// (undocumented)
212-
startLoad(startPosition: number): void;
212+
startLoad(startPosition: number, skipSeekToStartPosition?: boolean): void;
213213
// (undocumented)
214214
protected unregisterListeners(): void;
215215
}
@@ -402,7 +402,7 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP
402402
// (undocumented)
403403
protected doTick(): void;
404404
// (undocumented)
405-
filterReplacedPrimary(frag: MediaFragment | null, details: LevelDetails | undefined): MediaFragment | null;
405+
protected filterReplacedPrimary(frag: MediaFragment | null, details: LevelDetails | undefined): MediaFragment | null;
406406
// (undocumented)
407407
protected flushBufferGap(frag: Fragment): void;
408408
// (undocumented)
@@ -510,6 +510,8 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP
510510
// (undocumented)
511511
protected playlistType: PlaylistLevelType;
512512
// (undocumented)
513+
protected get primaryPrefetch(): boolean;
514+
// (undocumented)
513515
protected recoverWorkerError(data: ErrorData): void;
514516
// (undocumented)
515517
protected reduceLengthAndFlushBuffer(data: ErrorData): boolean;
@@ -557,6 +559,8 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP
557559
// (undocumented)
558560
protected _streamEnded(bufferInfo: BufferInfo, levelDetails: LevelDetails): boolean;
559561
// (undocumented)
562+
protected get timelineOffset(): number;
563+
// (undocumented)
560564
protected transmuxer: TransmuxerInterface | null;
561565
// (undocumented)
562566
protected unregisterListeners(): void;
@@ -2133,6 +2137,8 @@ export class HlsAssetPlayer {
21332137
// (undocumented)
21342138
get bufferedEnd(): number;
21352139
// (undocumented)
2140+
bufferedInPlaceToEnd(media?: HTMLMediaElement | null): boolean;
2141+
// (undocumented)
21362142
get currentTime(): number;
21372143
// (undocumented)
21382144
destroy(): void;
@@ -2689,6 +2695,8 @@ export class InterstitialEvent {
26892695
// (undocumented)
26902696
reset(): void;
26912697
// (undocumented)
2698+
resetOnResume?: boolean;
2699+
// (undocumented)
26922700
restrictions: PlaybackRestrictions;
26932701
// (undocumented)
26942702
resumeAnchor?: MediaFragmentRef;
@@ -2705,6 +2713,8 @@ export class InterstitialEvent {
27052713
// (undocumented)
27062714
get startDate(): Date;
27072715
// (undocumented)
2716+
get startIsAligned(): boolean;
2717+
// (undocumented)
27082718
get startOffset(): number;
27092719
// (undocumented)
27102720
get startTime(): number;
@@ -4544,7 +4554,7 @@ export class SubtitleStreamController extends BaseStreamController implements Ne
45444554
// (undocumented)
45454555
protected registerListeners(): void;
45464556
// (undocumented)
4547-
startLoad(startPosition: number): void;
4557+
startLoad(startPosition: number, skipSeekToStartPosition?: boolean): void;
45484558
// (undocumented)
45494559
protected unregisterListeners(): void;
45504560
}

src/controller/audio-stream-controller.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ class AudioStreamController
189189
);
190190
}
191191

192-
startLoad(startPosition: number) {
192+
startLoad(startPosition: number, skipSeekToStartPosition?: boolean) {
193193
if (!this.levels) {
194194
this.startPosition = startPosition;
195195
this.state = State.STOPPED;
@@ -209,11 +209,9 @@ class AudioStreamController
209209
} else {
210210
this.state = State.WAITING_TRACK;
211211
}
212-
this.nextLoadPosition =
213-
this.startPosition =
214-
this.lastCurrentTime =
215-
startPosition;
216-
212+
this.nextLoadPosition = this.lastCurrentTime =
213+
startPosition + this.timelineOffset;
214+
this.startPosition = skipSeekToStartPosition ? -1 : startPosition;
217215
this.tick();
218216
}
219217

@@ -325,7 +323,9 @@ class AudioStreamController
325323
// => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
326324
if (
327325
!this.buffering ||
328-
(!media && (this.startFragRequested || !config.startFragPrefetch)) ||
326+
(!media &&
327+
!this.primaryPrefetch &&
328+
(this.startFragRequested || !config.startFragPrefetch)) ||
329329
!levels?.[trackId]
330330
) {
331331
return;

src/controller/base-stream-controller.ts

+44-11
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,17 @@ export default class BaseStreamController
255255
}
256256
}
257257

258+
protected get timelineOffset(): number {
259+
const configuredTimelineOffset = this.config.timelineOffset;
260+
if (configuredTimelineOffset) {
261+
return (
262+
this.getLevelDetails()?.appliedTimelineOffset ||
263+
configuredTimelineOffset
264+
);
265+
}
266+
return 0;
267+
}
268+
258269
protected onMediaAttached(
259270
event: Events.MEDIA_ATTACHED,
260271
data: MediaAttachedData,
@@ -1299,9 +1310,8 @@ export default class BaseStreamController
12991310
: levelDetails.fragmentEnd;
13001311
frag = this.getFragmentAtPosition(pos, end, levelDetails);
13011312
}
1302-
1303-
frag = this.filterReplacedPrimary(frag, levelDetails);
1304-
return this.mapToInitFragWhenRequired(frag);
1313+
const programFrag = this.filterReplacedPrimary(frag, levelDetails);
1314+
return this.mapToInitFragWhenRequired(programFrag);
13051315
}
13061316

13071317
protected isLoopLoading(frag: Fragment, targetBufferTime: number): boolean {
@@ -1351,18 +1361,26 @@ export default class BaseStreamController
13511361
return nextFragment;
13521362
}
13531363

1354-
filterReplacedPrimary(
1364+
protected get primaryPrefetch(): boolean {
1365+
if (interstitialsEnabled(this.hls.config)) {
1366+
const playingInterstitial =
1367+
this.hls.interstitialsManager?.playingItem?.event;
1368+
if (playingInterstitial) {
1369+
return true;
1370+
}
1371+
}
1372+
return false;
1373+
}
1374+
1375+
protected filterReplacedPrimary(
13551376
frag: MediaFragment | null,
13561377
details: LevelDetails | undefined,
13571378
): MediaFragment | null {
13581379
if (!frag) {
13591380
return frag;
13601381
}
1361-
const config = this.hls.config;
13621382
if (
1363-
__USE_INTERSTITIALS__ &&
1364-
config.interstitialsController &&
1365-
config.enableInterstitialPlayback !== false &&
1383+
interstitialsEnabled(this.hls.config) &&
13661384
frag.type !== PlaylistLevelType.SUBTITLE
13671385
) {
13681386
// Do not load fragments outside the buffering schedule segment
@@ -1388,7 +1406,13 @@ export default class BaseStreamController
13881406
}
13891407
if (frag.start > bufferingItem.end && bufferingItem.nextEvent) {
13901408
// fragment is past schedule item end
1391-
return null;
1409+
// allow some overflow when not appending in place to prevent stalls
1410+
if (
1411+
bufferingItem.nextEvent.appendInPlace ||
1412+
frag.start - bufferingItem.end > 1
1413+
) {
1414+
return null;
1415+
}
13921416
}
13931417
}
13941418
}
@@ -1659,6 +1683,7 @@ export default class BaseStreamController
16591683
if (startPosition < sliding) {
16601684
startPosition = -1;
16611685
}
1686+
const timelineOffset = this.timelineOffset;
16621687
if (startPosition === -1) {
16631688
// Use Playlist EXT-X-START:TIME-OFFSET when set
16641689
// Prioritize Multivariant Playlist offset so that main, audio, and subtitle stream-controller start times match
@@ -1693,9 +1718,9 @@ export default class BaseStreamController
16931718
this.log(`setting startPosition to 0 by default`);
16941719
this.startPosition = startPosition = 0;
16951720
}
1696-
this.lastCurrentTime = startPosition;
1721+
this.lastCurrentTime = startPosition + timelineOffset;
16971722
}
1698-
this.nextLoadPosition = startPosition;
1723+
this.nextLoadPosition = startPosition + timelineOffset;
16991724
}
17001725

17011726
protected getLoadPosition(): number {
@@ -2066,3 +2091,11 @@ export default class BaseStreamController
20662091
return this._state;
20672092
}
20682093
}
2094+
2095+
function interstitialsEnabled(config: HlsConfig): boolean {
2096+
return (
2097+
__USE_INTERSTITIALS__ &&
2098+
!!config.interstitialsController &&
2099+
config.enableInterstitialPlayback !== false
2100+
);
2101+
}

src/controller/buffer-controller.ts

+21-13
Original file line numberDiff line numberDiff line change
@@ -1051,17 +1051,10 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe
10511051
!this.sourceBuffers.some(([type]) => type && !this.tracks[type]?.ended);
10521052

10531053
if (allTracksEnding) {
1054-
this.log(`Queueing EOS`);
1055-
this.blockUntilOpen(() => {
1056-
this.sourceBuffers.forEach(([type]) => {
1057-
if (type !== null) {
1058-
const track = this.tracks[type];
1059-
if (track) {
1060-
track.ending = false;
1061-
}
1062-
}
1063-
});
1064-
if (allowEndOfStream) {
1054+
if (allowEndOfStream) {
1055+
this.log(`Queueing EOS`);
1056+
this.blockUntilOpen(() => {
1057+
this.tracksEnded();
10651058
const { mediaSource } = this;
10661059
if (!mediaSource || mediaSource.readyState !== 'open') {
10671060
if (mediaSource) {
@@ -1074,12 +1067,27 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe
10741067
this.log(`Calling mediaSource.endOfStream()`);
10751068
// Allow this to throw and be caught by the enqueueing function
10761069
mediaSource.endOfStream();
1077-
}
1070+
1071+
this.hls.trigger(Events.BUFFERED_TO_END, undefined);
1072+
});
1073+
} else {
1074+
this.tracksEnded();
10781075
this.hls.trigger(Events.BUFFERED_TO_END, undefined);
1079-
});
1076+
}
10801077
}
10811078
}
10821079

1080+
private tracksEnded() {
1081+
this.sourceBuffers.forEach(([type]) => {
1082+
if (type !== null) {
1083+
const track = this.tracks[type];
1084+
if (track) {
1085+
track.ending = false;
1086+
}
1087+
}
1088+
});
1089+
}
1090+
10831091
private onLevelUpdated(
10841092
event: Events.LEVEL_UPDATED,
10851093
{ details }: LevelUpdatedData,

src/controller/interstitial-player.ts

+30
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export class HlsAssetPlayer {
2828
private hasDetails: boolean = false;
2929
private mediaAttached: HTMLMediaElement | null = null;
3030
private _currentTime?: number;
31+
private _bufferedEosTime?: number;
3132

3233
constructor(
3334
HlsPlayerClass: typeof Hls,
@@ -62,6 +63,22 @@ export class HlsAssetPlayer {
6263
});
6364
}
6465

66+
bufferedInPlaceToEnd(media?: HTMLMediaElement | null) {
67+
if (!this.interstitial.appendInPlace) {
68+
return false;
69+
}
70+
if (this.hls?.bufferedToEnd) {
71+
return true;
72+
}
73+
if (!media || !this._bufferedEosTime) {
74+
return false;
75+
}
76+
const start = this.timelineOffset;
77+
const bufferInfo = BufferHelper.bufferInfo(media, start, 0);
78+
const bufferedEnd = this.getAssetTime(bufferInfo.end);
79+
return bufferedEnd >= this._bufferedEosTime - 0.02;
80+
}
81+
6582
private checkPlayout = () => {
6683
const interstitial = this.interstitial;
6784
const playoutLimit = interstitial.playoutLimit;
@@ -90,6 +107,9 @@ export class HlsAssetPlayer {
90107
get bufferedEnd(): number {
91108
const media = this.media || this.mediaAttached;
92109
if (!media) {
110+
if (this._bufferedEosTime) {
111+
return this._bufferedEosTime;
112+
}
93113
return this.currentTime;
94114
}
95115
const bufferInfo = BufferHelper.bufferInfo(media, media.currentTime, 0.001);
@@ -153,10 +173,19 @@ export class HlsAssetPlayer {
153173
const media = this.mediaAttached;
154174
if (media) {
155175
this._currentTime = media.currentTime;
176+
this.bufferSnapShot();
156177
media.removeEventListener('timeupdate', this.checkPlayout);
157178
}
158179
}
159180

181+
private bufferSnapShot() {
182+
if (this.mediaAttached) {
183+
if (this.hls?.bufferedToEnd) {
184+
this._bufferedEosTime = this.bufferedEnd;
185+
}
186+
}
187+
}
188+
160189
destroy() {
161190
this.removeMediaListeners();
162191
this.hls.destroy();
@@ -185,6 +214,7 @@ export class HlsAssetPlayer {
185214
}
186215

187216
transferMedia() {
217+
this.bufferSnapShot();
188218
return this.hls.transferMedia();
189219
}
190220

0 commit comments

Comments
 (0)