diff --git a/src/controller/fragment-tracker.ts b/src/controller/fragment-tracker.ts index 348aa47eb24..5672f4af16d 100644 --- a/src/controller/fragment-tracker.ts +++ b/src/controller/fragment-tracker.ts @@ -208,6 +208,15 @@ export class FragmentTracker implements ComponentAPI { } } + public fragBuffered(frag: Fragment) { + const fragKey = getFragmentKey(frag); + const fragmentEntity = this.fragments[fragKey]; + if (fragmentEntity) { + fragmentEntity.backtrack = fragmentEntity.loaded = null; + fragmentEntity.buffered = true; + } + } + private getBufferedTimes( fragment: Fragment, part: Part | null, @@ -412,6 +421,29 @@ export class FragmentTracker implements ComponentAPI { return !!this.fragments[fragKey]; } + public removeFragmentsInRange( + start: number, + end: number, + playlistType: PlaylistLevelType + ) { + Object.keys(this.fragments).forEach((key) => { + const fragmentEntity = this.fragments[key]; + if (!fragmentEntity) { + return; + } + if (fragmentEntity.buffered) { + const frag = fragmentEntity.body; + if ( + frag.type === playlistType && + frag.start < end && + frag.end > start + ) { + this.removeFragment(frag); + } + } + }); + } + public removeFragment(fragment: Fragment) { const fragKey = getFragmentKey(fragment); fragment.stats.loaded = 0; diff --git a/src/controller/subtitle-stream-controller.ts b/src/controller/subtitle-stream-controller.ts index 5e73b46b0cf..67c9c4935d7 100644 --- a/src/controller/subtitle-stream-controller.ts +++ b/src/controller/subtitle-stream-controller.ts @@ -18,6 +18,7 @@ import type { SubtitleTracksUpdatedData, TrackLoadedData, TrackSwitchedData, + BufferFlushingData, } from '../types/events'; const TICK_INTERVAL = 500; // how often to tick in ms @@ -50,6 +51,7 @@ export class SubtitleStreamController hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); hls.on(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this); + hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); } private _unregisterListeners() { @@ -61,6 +63,7 @@ export class SubtitleStreamController hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); hls.off(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this); + hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); } startLoad() { @@ -97,7 +100,7 @@ export class SubtitleStreamController } // Create/update a buffered array matching the interface used by BufferHelper.bufferedInfo - // so we can re-use the logic used to detect how much have been buffered + // so we can re-use the logic used to detect how much has been buffered let timeRange: TimeRange | undefined; const fragStart = frag.start; for (let i = 0; i < buffered.length; i++) { @@ -117,6 +120,33 @@ export class SubtitleStreamController }; buffered.push(timeRange); } + this.fragmentTracker.fragBuffered(frag); + } + + onBufferFlushing( + event: Events.BUFFER_FLUSHING, + { startOffset, endOffset }: BufferFlushingData + ) { + if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) { + this.tracksBuffered.forEach((buffered) => { + for (let i = 0; i < buffered.length; ) { + if (buffered[i].end <= endOffset) { + buffered.shift(); + continue; + } else if (buffered[i].start < endOffset) { + buffered[i].start = endOffset; + } else { + break; + } + i++; + } + }); + this.fragmentTracker.removeFragmentsInRange( + startOffset, + endOffset, + PlaylistLevelType.SUBTITLE + ); + } } // If something goes wrong, proceed to next frag, if we were processing one. @@ -246,7 +276,7 @@ export class SubtitleStreamController } if (this.state === State.IDLE) { - const { config, currentTrackId, fragmentTracker, media, levels } = this; + const { currentTrackId, levels } = this; if ( !levels.length || !levels[currentTrackId] || @@ -255,6 +285,7 @@ export class SubtitleStreamController return; } + const { config, media } = this; const bufferedInfo = BufferHelper.bufferedInfo( this.mediaBufferTimeRanges, media.currentTime, @@ -314,7 +345,7 @@ export class SubtitleStreamController this.hls.trigger(Events.KEY_LOADING, { frag: foundFrag }); } else if ( foundFrag && - fragmentTracker.getState(foundFrag) === FragmentState.NOT_LOADED + this.fragmentTracker.getState(foundFrag) === FragmentState.NOT_LOADED ) { // only load if fragment is not loaded this.loadFragment(foundFrag, trackDetails, targetBufferTime); @@ -331,7 +362,7 @@ export class SubtitleStreamController super.loadFragment(frag, levelDetails, targetBufferTime); } - get mediaBufferTimeRanges() { + get mediaBufferTimeRanges(): TimeRange[] { return this.tracksBuffered[this.currentTrackId] || []; } } diff --git a/src/controller/timeline-controller.ts b/src/controller/timeline-controller.ts index 6f8c7f18a89..c0a99e29686 100644 --- a/src/controller/timeline-controller.ts +++ b/src/controller/timeline-controller.ts @@ -627,18 +627,27 @@ export class TimelineController implements ComponentAPI { event: Events.BUFFER_FLUSHING, { startOffset, endOffset, type }: BufferFlushingData ) { - // Clear 608 CC cues from the back buffer + const { media } = this; + if (!media || media.currentTime < endOffset) { + return; + } + // Clear 608 caption cues from the captions TextTracks when the video back buffer is flushed // Forward cues are never removed because we can loose streamed 608 content from recent fragments if (!type || type === 'video') { - const { media } = this; - if (!media || media.currentTime < endOffset) { - return; - } const { captionsTracks } = this; Object.keys(captionsTracks).forEach((trackName) => removeCuesInRange(captionsTracks[trackName], startOffset, endOffset) ); } + if (this.config.renderTextTracksNatively) { + // Clear VTT/IMSC1 subtitle cues from the subtitle TextTracks when the back buffer is flushed + if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) { + const { textTracks } = this; + Object.keys(textTracks).forEach((trackName) => + removeCuesInRange(textTracks[trackName], startOffset, endOffset) + ); + } + } } private extractCea608Data(byteArray: Uint8Array): number[][] {