Skip to content

Commit

Permalink
Fix Low-Latency part and fragment tracking (#5423)
Browse files Browse the repository at this point in the history
* Fix Low-Latency part and fragment tracking regression introduced in #5102

* Fix issues with reuse of player instance when loading additional assets
Fixes #5425

* Reset SourceBuffers in `loadSource()` when the asset URL has changed, or buffer-controller setup has begun

* Remove old parts from the fragment tracker on frag buffered

* Remove old parts from the fragment tracker on frag buffered

* Reset eme-controller key format promise on manifest loading
  • Loading branch information
robwalch authored May 3, 2023
1 parent 89ca104 commit f4629d3
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 153 deletions.
2 changes: 2 additions & 0 deletions api-extractor/report/hls.js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP
// (undocumented)
protected fragPrevious: Fragment | null;
// (undocumented)
protected getAppendedFrag(position: number, playlistType?: PlaylistLevelType): Fragment | null;
// (undocumented)
protected getCurrentContext(chunkMeta: ChunkMetadata): {
frag: Fragment;
part: Part | null;
Expand Down
28 changes: 18 additions & 10 deletions src/controller/audio-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,10 +383,7 @@ class AudioStreamController
(!mainBufferInfo?.len && bufferInfo.len)
) {
// Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo
const mainFrag = this.fragmentTracker.getBufferedFrag(
frag.start,
PlaylistLevelType.MAIN
);
const mainFrag = this.getAppendedFrag(frag.start, PlaylistLevelType.MAIN);
if (mainFrag === null) {
return;
}
Expand Down Expand Up @@ -464,12 +461,18 @@ class AudioStreamController
}

onManifestLoading() {
this.mainDetails = null;
this.fragmentTracker.removeAllFragments();
this.startPosition = this.lastCurrentTime = 0;
this.bufferFlushed = false;
this.bufferedTrack = null;
this.switchingTrack = null;
this.levels =
this.mainDetails =
this.waitingData =
this.bufferedTrack =
this.cachedTrackLoadedData =
this.switchingTrack =
null;
this.startFragRequested = false;
this.trackId = this.videoTrackCC = this.waitingVideoCC = -1;
}

onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
Expand All @@ -492,7 +495,11 @@ class AudioStreamController
return;
}
this.log(
`Track ${trackId} loaded [${newDetails.startSN},${newDetails.endSN}],duration:${newDetails.totalduration}`
`Track ${trackId} loaded [${newDetails.startSN},${newDetails.endSN}]${
newDetails.lastPartSn
? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]`
: ''
},duration:${newDetails.totalduration}`
);

const track = levels[trackId];
Expand Down Expand Up @@ -760,9 +767,10 @@ class AudioStreamController
}

if (initSegment?.tracks) {
this._bufferInitSegment(initSegment.tracks, frag, chunkMeta);
const mapFragment = frag.initSegment || frag;
this._bufferInitSegment(initSegment.tracks, mapFragment, chunkMeta);
hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, {
frag,
frag: mapFragment,
id,
tracks: initSegment.tracks,
});
Expand Down
56 changes: 44 additions & 12 deletions src/controller/base-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export default class BaseStreamController

public stopLoad() {
this.fragmentLoader.abort();
this.keyLoader.abort();
this.keyLoader.abort(this.playlistType);
const frag = this.fragCurrent;
if (frag?.loader) {
frag.abortRequests();
Expand Down Expand Up @@ -266,8 +266,9 @@ export default class BaseStreamController
'seeking outside of buffer while fragment load in progress, cancel fragment load'
);
fragCurrent.abortRequests();
this.resetLoadingState();
}
this.resetLoadingState();
this.fragPrevious = null;
}
}
}
Expand Down Expand Up @@ -399,7 +400,8 @@ export default class BaseStreamController
}

protected clearTrackerIfNeeded(frag: Fragment) {
const fragState = this.fragmentTracker.getState(frag);
const { fragmentTracker } = this;
const fragState = fragmentTracker.getState(frag);
if (fragState === FragmentState.APPENDING) {
// Lower the buffer size and try again
const playlistType = frag.type as PlaylistLevelType;
Expand All @@ -412,11 +414,22 @@ export default class BaseStreamController
bufferedInfo ? bufferedInfo.len : this.config.maxBufferLength
);
if (this.reduceMaxBufferLength(minForwardBufferLength)) {
this.fragmentTracker.removeFragment(frag);
fragmentTracker.removeFragment(frag);
}
} else if (this.mediaBuffer?.buffered.length === 0) {
// Stop gap for bad tracker / buffer flush behavior
this.fragmentTracker.removeAllFragments();
fragmentTracker.removeAllFragments();
} else if (fragmentTracker.hasParts(frag.type)) {
// In low latency mode, remove fragments for which only some parts were buffered
fragmentTracker.detectPartialFragments({
frag,
part: null,
stats: frag.stats,
id: frag.type,
});
if (fragmentTracker.getState(frag) === FragmentState.PARTIAL) {
fragmentTracker.removeFragment(frag);
}
}
}

Expand Down Expand Up @@ -543,9 +556,9 @@ export default class BaseStreamController
this.log(
`Buffered ${frag.type} sn: ${frag.sn}${
part ? ' part: ' + part.index : ''
} of ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${
frag.level
} (frag:[${(frag.startPTS ?? NaN).toFixed(3)}-${(
} of ${
this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
} ${frag.level} (frag:[${(frag.startPTS ?? NaN).toFixed(3)}-${(
frag.endPTS ?? NaN
).toFixed(3)}] > buffer:${
media
Expand Down Expand Up @@ -640,7 +653,7 @@ export default class BaseStreamController
}

targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
if (this.config.lowLatencyMode) {
if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
const partList = details.partList;
if (partList && progressCallback) {
if (targetBufferTime > frag.end && details.fragmentHint) {
Expand Down Expand Up @@ -990,6 +1003,20 @@ export default class BaseStreamController
return false;
}

protected getAppendedFrag(
position: number,
playlistType: PlaylistLevelType = PlaylistLevelType.MAIN
): Fragment | null {
const fragOrPart = this.fragmentTracker.getAppendedFrag(
position,
PlaylistLevelType.MAIN
);
if (fragOrPart && 'fragment' in fragOrPart) {
return fragOrPart.fragment;
}
return fragOrPart;
}

protected getNextFragment(
pos: number,
levelDetails: LevelDetails
Expand Down Expand Up @@ -1215,10 +1242,11 @@ export default class BaseStreamController
let { fragments, endSN } = levelDetails;
const { fragmentHint } = levelDetails;
const tolerance = config.maxFragLookUpTolerance;
const partList = levelDetails.partList;

const loadingParts = !!(
config.lowLatencyMode &&
levelDetails.partList &&
partList?.length &&
fragmentHint
);
if (loadingParts && fragmentHint && !this.bitrateTest) {
Expand Down Expand Up @@ -1254,7 +1282,11 @@ export default class BaseStreamController
) {
fragPrevious = frag;
}
if (fragPrevious && frag.sn === fragPrevious.sn && !loadingParts) {
if (
fragPrevious &&
frag.sn === fragPrevious.sn &&
(!loadingParts || partList[0].fragment.sn > frag.sn)
) {
// Force the next fragment to load if the previous one was already selected. This can occasionally happen with
// non-uniform fragment durations
const sameLevel = fragPrevious && frag.level === fragPrevious.level;
Expand Down Expand Up @@ -1420,7 +1452,7 @@ export default class BaseStreamController
private handleFragLoadAborted(frag: Fragment, part: Part | undefined) {
if (this.transmuxer && frag.sn !== 'initSegment' && frag.stats.aborted) {
this.warn(
`Fragment ${frag.sn}${part ? ' part' + part.index : ''} of level ${
`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${
frag.level
} was aborted`
);
Expand Down
8 changes: 7 additions & 1 deletion src/controller/buffer-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export default class BufferController implements ComponentAPI {
const { hls } = this;
hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
hls.on(Events.BUFFER_APPENDING, this.onBufferAppending, this);
Expand All @@ -103,6 +104,7 @@ export default class BufferController implements ComponentAPI {
const { hls } = this;
hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
hls.off(Events.BUFFER_APPENDING, this.onBufferAppending, this);
Expand All @@ -125,6 +127,11 @@ export default class BufferController implements ComponentAPI {
this.lastMpegAudioChunk = null;
}

private onManifestLoading() {
this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = 0;
this.details = null;
}

protected onManifestParsed(
event: Events.MANIFEST_PARSED,
data: ManifestParsedData
Expand All @@ -138,7 +145,6 @@ export default class BufferController implements ComponentAPI {
codecEvents = 1;
}
this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = codecEvents;
this.details = null;
logger.log(
`${this.bufferCodecEventsExpected} bufferCodec event(s) expected`
);
Expand Down
6 changes: 6 additions & 0 deletions src/controller/eme-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,14 @@ class EMEController implements ComponentAPI {
private registerListeners() {
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
this.hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this);
this.hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
this.hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
}

private unregisterListeners() {
this.hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
this.hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this);
this.hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
}

Expand Down Expand Up @@ -1181,6 +1183,10 @@ class EMEController implements ComponentAPI {
});
}

private onManifestLoading() {
this.keyFormatPromise = null;
}

private onManifestLoaded(
event: Events.MANIFEST_LOADED,
{ sessionKeys }: ManifestLoadedData
Expand Down
Loading

0 comments on commit f4629d3

Please sign in to comment.