Fix ReplacingCuesResolver.discardCuesBeforeTimeUs to retain active cue

The method previously discarded the cue that was active at `timeUs`,
meaning it had started before but had not ended by `timeUs`.

Issue: androidx/media#1939

PiperOrigin-RevId: 702707611
(cherry picked from commit e927d7b986)
This commit is contained in:
rohks 2024-12-04 06:48:43 -08:00 committed by Shahd AbuDaghash
parent 121b79ae96
commit 5d9badcb50
3 changed files with 57 additions and 3 deletions

View file

@ -38,6 +38,13 @@
when provided while processing `onOutputFormatChanged` when provided while processing `onOutputFormatChanged`
([#1371](https://github.com/androidx/media/pull/1371)). ([#1371](https://github.com/androidx/media/pull/1371)).
* Text: * Text:
* Stop eagerly loading all subtitle files configured with
`MediaItem.Builder.setSubtitleConfigurations`, and instead only load one
if it is selected by track selection
([#1721](https://github.com/androidx/media/issues/1721)).
* Fix bug in `ReplacingCuesResolver.discardCuesBeforeTimeUs` where the cue
active at `timeUs` (started before but not yet ended) was incorrectly
discarded ([#1939](https://github.com/androidx/media/issues/1939)).
* Metadata: * Metadata:
* Image: * Image:
* DRM: * DRM:

View file

@ -80,9 +80,15 @@ import java.util.ArrayList;
@Override @Override
public void discardCuesBeforeTimeUs(long timeUs) { public void discardCuesBeforeTimeUs(long timeUs) {
int indexToDiscardTo = getIndexOfCuesStartingAfter(timeUs); int indexToDiscardTo = getIndexOfCuesStartingAfter(timeUs);
if (indexToDiscardTo > 0) { if (indexToDiscardTo == 0) {
cuesWithTimingList.subList(0, indexToDiscardTo).clear(); // Either the first cue starts after timeUs, or the cues list is empty.
return;
} }
CuesWithTiming lastCueToDiscard = cuesWithTimingList.get(indexToDiscardTo - 1);
if (lastCueToDiscard.endTimeUs == C.TIME_UNSET || lastCueToDiscard.endTimeUs >= timeUs) {
indexToDiscardTo--;
}
cuesWithTimingList.subList(0, indexToDiscardTo).clear();
} }
@Override @Override
@ -142,7 +148,7 @@ import java.util.ArrayList;
/** /**
* Returns the index of the first {@link CuesWithTiming} in {@link #cuesWithTimingList} where * Returns the index of the first {@link CuesWithTiming} in {@link #cuesWithTimingList} where
* {@link CuesWithTiming#startTimeUs} is strictly less than {@code timeUs}. * {@link CuesWithTiming#startTimeUs} is strictly greater than {@code timeUs}.
* *
* <p>Returns the size of {@link #cuesWithTimingList} if all cues are before timeUs * <p>Returns the size of {@link #cuesWithTimingList} if all cues are before timeUs
*/ */

View file

@ -217,6 +217,47 @@ public final class ReplacingCuesResolverTest {
assertThat(replacingCuesResolver.getNextCueChangeTimeUs(4_999_990)).isEqualTo(6_000_000); assertThat(replacingCuesResolver.getNextCueChangeTimeUs(4_999_990)).isEqualTo(6_000_000);
} }
@Test
public void discardCuesBeforeTimeUs_retainsActiveCueWithSetDuration() {
ReplacingCuesResolver replacingCuesResolver = new ReplacingCuesResolver();
CuesWithTiming activeCue =
new CuesWithTiming(FIRST_CUES, /* startTimeUs= */ 3_000_000, /* durationUs= */ 4_000_000);
CuesWithTiming laterCue =
new CuesWithTiming(SECOND_CUES, /* startTimeUs= */ 8_000_000, /* durationUs= */ 2_000_000);
replacingCuesResolver.addCues(activeCue, /* currentPositionUs= */ 5_000_000);
replacingCuesResolver.addCues(laterCue, /* currentPositionUs= */ 5_000_000);
// Discard cues before 5_000_000. activeCue should remain active because it ends at 7_000_000.
replacingCuesResolver.discardCuesBeforeTimeUs(5_000_000);
// Query at a time within activeCue's range to verify it's still there.
assertThat(replacingCuesResolver.getCuesAtTimeUs(6_000_000)).isEqualTo(FIRST_CUES);
// Ensure that laterCue is unaffected.
assertThat(replacingCuesResolver.getCuesAtTimeUs(9_000_000)).isEqualTo(SECOND_CUES);
}
@Test
public void discardCuesBeforeTimeUs_retainsActiveCueWithUnsetDuration() {
ReplacingCuesResolver replacingCuesResolver = new ReplacingCuesResolver();
CuesWithTiming activeCue =
new CuesWithTiming(
FIRST_CUES, /* startTimeUs= */ 3_000_000, /* durationUs= */ C.TIME_UNSET);
CuesWithTiming laterCue =
new CuesWithTiming(SECOND_CUES, /* startTimeUs= */ 8_000_000, /* durationUs= */ 2_000_000);
replacingCuesResolver.addCues(activeCue, /* currentPositionUs= */ 5_000_000);
replacingCuesResolver.addCues(laterCue, /* currentPositionUs= */ 5_000_000);
// Discard cues before 5_000_000. activeCue should remain active because its
// duration is unset, meaning it should remain visible until replaced by a subsequent cue
// starting at 8_000_000.
replacingCuesResolver.discardCuesBeforeTimeUs(5_000_000);
// Query at a time within activeCue's range to verify it's still there.
assertThat(replacingCuesResolver.getCuesAtTimeUs(6_000_000)).isEqualTo(FIRST_CUES);
// Ensure that laterCue is unaffected.
assertThat(replacingCuesResolver.getCuesAtTimeUs(9_000_000)).isEqualTo(SECOND_CUES);
}
@Test @Test
public void clear_clearsAllCues() { public void clear_clearsAllCues() {
ReplacingCuesResolver replacingCuesResolver = new ReplacingCuesResolver(); ReplacingCuesResolver replacingCuesResolver = new ReplacingCuesResolver();