From 08fa93599c7cd5fec31533dd6c2c470be14b1e80 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Tue, 17 Jan 2023 20:30:28 -0800 Subject: [PATCH] Fix currentLevel and FRAG_CHANGED reporting when seeking back from edge in Low-Latency streams (#5102) Related to #5085 and #5077 --- api-extractor/report/hls.js.api.md | 2 - src/controller/fragment-tracker.ts | 88 ++++++++++++++++++------------ src/controller/level-helper.ts | 1 - src/loader/fragment.ts | 2 - src/types/fragment-tracker.ts | 3 + 5 files changed, 56 insertions(+), 40 deletions(-) diff --git a/api-extractor/report/hls.js.api.md b/api-extractor/report/hls.js.api.md index e494638a1f5..b54d21fb176 100644 --- a/api-extractor/report/hls.js.api.md +++ b/api-extractor/report/hls.js.api.md @@ -745,8 +745,6 @@ export class Fragment extends BaseSegment { // (undocumented) abortRequests(): void; // (undocumented) - appendedPTS?: number; - // (undocumented) bitrateTest: boolean; // (undocumented) cc: number; diff --git a/src/controller/fragment-tracker.ts b/src/controller/fragment-tracker.ts index 359dbc93010..f4904e51567 100644 --- a/src/controller/fragment-tracker.ts +++ b/src/controller/fragment-tracker.ts @@ -23,7 +23,7 @@ export enum FragmentState { } export class FragmentTracker implements ComponentAPI { - private activeFragment: Fragment | null = null; + private mainFragEntity: FragmentEntity | null = null; private activeParts: Part[] | null = null; private endListFragments: { [key in PlaylistLevelType]?: FragmentEntity } = Object.create(null); @@ -65,7 +65,7 @@ export class FragmentTracker implements ComponentAPI { // @ts-ignore this.endListFragments = this.timeRanges = - this.activeFragment = + this.mainFragEntity = this.activeParts = null; } @@ -79,34 +79,33 @@ export class FragmentTracker implements ComponentAPI { levelType: PlaylistLevelType ): Fragment | Part | null { if (levelType === PlaylistLevelType.MAIN) { - const { activeFragment, activeParts } = this; - if (!activeFragment) { - return null; - } - if (activeParts) { - for (let i = activeParts.length; i--; ) { - const activePart = activeParts[i]; - const appendedPTS = activePart - ? activePart.end - : activeFragment.appendedPTS; - if ( - activePart.start <= position && - appendedPTS !== undefined && - position <= appendedPTS - ) { - // 9 is a magic number. remove parts from lookup after a match but keep some short seeks back. - if (i > 9) { - this.activeParts = activeParts.slice(i - 9); + const { mainFragEntity, activeParts } = this; + if (mainFragEntity) { + if (mainFragEntity && activeParts) { + for (let i = activeParts.length; i--; ) { + const activePart = activeParts[i]; + const appendedPTS = activePart + ? activePart.end + : mainFragEntity.appendedPTS; + if ( + activePart.start <= position && + appendedPTS !== null && + position <= appendedPTS + ) { + // 9 is a magic number. remove parts from lookup after a match but keep some short seeks back. + if (i > 9) { + this.activeParts = activeParts.slice(i - 9); + } + return activePart; } - return activePart; } + } else if ( + mainFragEntity.body.start <= position && + mainFragEntity.appendedPTS !== null && + position <= mainFragEntity.appendedPTS + ) { + return mainFragEntity.body; } - } else if ( - activeFragment.start <= position && - activeFragment.appendedPTS !== undefined && - position <= activeFragment.appendedPTS - ) { - return activeFragment; } } return this.getBufferedFrag(position, levelType); @@ -362,6 +361,7 @@ export class FragmentTracker implements ComponentAPI { const fragKey = getFragmentKey(frag); this.fragments[fragKey] = { body: frag, + appendedPTS: null, loaded: data, buffered: false, range: Object.create(null), @@ -373,10 +373,23 @@ export class FragmentTracker implements ComponentAPI { data: BufferAppendedData ) { const { frag, part, timeRanges } = data; + let mainFragEntity = this.mainFragEntity; if (frag.type === PlaylistLevelType.MAIN) { - if (this.activeFragment !== frag) { - this.activeFragment = frag; - frag.appendedPTS = undefined; + const lastMainFrag = mainFragEntity ? mainFragEntity.body : null; + if (lastMainFrag !== frag) { + if (mainFragEntity && lastMainFrag && lastMainFrag.sn !== frag.sn) { + // archive frag for getBufferedFrag() + mainFragEntity.buffered = true; + this.fragments[getFragmentKey(lastMainFrag)] = mainFragEntity; + } + const fragKey = getFragmentKey(frag); + mainFragEntity = this.mainFragEntity = this.fragments[fragKey] || { + body: frag, + appendedPTS: null, + loaded: null, + buffered: false, + range: Object.create(null), + }; } if (part) { let activeParts = this.activeParts; @@ -393,7 +406,7 @@ export class FragmentTracker implements ComponentAPI { Object.keys(timeRanges).forEach((elementaryStream: SourceBufferName) => { const timeRange = timeRanges[elementaryStream] as TimeRanges; this.detectEvictedFragments(elementaryStream, timeRange); - if (!part && frag.type === PlaylistLevelType.MAIN) { + if (!part && mainFragEntity) { const streamInfo = frag.elementaryStreams[elementaryStream]; if (!streamInfo) { return; @@ -401,9 +414,12 @@ export class FragmentTracker implements ComponentAPI { for (let i = 0; i < timeRange.length; i++) { const rangeEnd = timeRange.end(i); if (rangeEnd <= streamInfo.endPTS && rangeEnd > streamInfo.startPTS) { - frag.appendedPTS = Math.max(rangeEnd, frag.appendedPTS || 0); + mainFragEntity.appendedPTS = Math.max( + rangeEnd, + mainFragEntity.appendedPTS || 0 + ); } else { - frag.appendedPTS = streamInfo.endPTS; + mainFragEntity.appendedPTS = streamInfo.endPTS; } } } @@ -446,7 +462,9 @@ export class FragmentTracker implements ComponentAPI { const fragKey = getFragmentKey(fragment); fragment.stats.loaded = 0; fragment.clearElementaryStreamInfo(); - fragment.appendedPTS = undefined; + if (this.mainFragEntity === this.fragments[fragKey]) { + this.mainFragEntity = null; + } delete this.fragments[fragKey]; if (fragment.endList) { delete this.endListFragments[fragment.type]; @@ -456,7 +474,7 @@ export class FragmentTracker implements ComponentAPI { public removeAllFragments() { this.fragments = Object.create(null); this.endListFragments = Object.create(null); - this.activeFragment = null; + this.mainFragEntity = null; this.activeParts = null; } } diff --git a/src/controller/level-helper.ts b/src/controller/level-helper.ts index 495c1435b47..028c7f5efee 100644 --- a/src/controller/level-helper.ts +++ b/src/controller/level-helper.ts @@ -199,7 +199,6 @@ export function mergeDetails( ) { newFrag.start = newFrag.startPTS = oldFrag.startPTS as number; newFrag.startDTS = oldFrag.startDTS; - newFrag.appendedPTS = oldFrag.appendedPTS; newFrag.maxStartPTS = oldFrag.maxStartPTS; newFrag.endPTS = oldFrag.endPTS; diff --git a/src/loader/fragment.ts b/src/loader/fragment.ts index 26257fdf3d5..6146a7943df 100644 --- a/src/loader/fragment.ts +++ b/src/loader/fragment.ts @@ -120,8 +120,6 @@ export class Fragment extends BaseSegment { public startPTS?: number; // The ending Presentation Time Stamp (PTS) of the fragment. Set after transmux complete. public endPTS?: number; - // The latest Presentation Time Stamp (PTS) appended to the buffer. - public appendedPTS?: number; // The starting Decode Time Stamp (DTS) of the fragment. Set after transmux complete. public startDTS!: number; // The ending Decode Time Stamp (DTS) of the fragment. Set after transmux complete. diff --git a/src/types/fragment-tracker.ts b/src/types/fragment-tracker.ts index f83c40c5626..cd778c11170 100644 --- a/src/types/fragment-tracker.ts +++ b/src/types/fragment-tracker.ts @@ -4,6 +4,9 @@ import type { FragLoadedData } from './events'; export interface FragmentEntity { body: Fragment; + // appendedPTS is the latest buffered presentation time within the fragment's time range. + // It is used to determine: which fragment is appended at any given position, and hls.currentLevel. + appendedPTS: number | null; loaded: FragLoadedData | null; buffered: boolean; range: { [key in SourceBufferName]: FragmentBufferedRange };