Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove subtitle cues and data with the back-buffer #3969

Merged
merged 1 commit into from Jun 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/controller/fragment-tracker.ts
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
39 changes: 35 additions & 4 deletions src/controller/subtitle-stream-controller.ts
Expand Up @@ -18,6 +18,7 @@ import type {
SubtitleTracksUpdatedData,
TrackLoadedData,
TrackSwitchedData,
BufferFlushingData,
} from '../types/events';

const TICK_INTERVAL = 500; // how often to tick in ms
Expand Down Expand Up @@ -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() {
Expand All @@ -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() {
Expand Down Expand Up @@ -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++) {
Expand All @@ -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.
Expand Down Expand Up @@ -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] ||
Expand All @@ -255,6 +285,7 @@ export class SubtitleStreamController
return;
}

const { config, media } = this;
const bufferedInfo = BufferHelper.bufferedInfo(
this.mediaBufferTimeRanges,
media.currentTime,
Expand Down Expand Up @@ -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);
Expand All @@ -331,7 +362,7 @@ export class SubtitleStreamController
super.loadFragment(frag, levelDetails, targetBufferTime);
}

get mediaBufferTimeRanges() {
get mediaBufferTimeRanges(): TimeRange[] {
return this.tracksBuffered[this.currentTrackId] || [];
}
}
19 changes: 14 additions & 5 deletions src/controller/timeline-controller.ts
Expand Up @@ -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[][] {
Expand Down