Fix IndexOutOfBoundsException in LegacySubtitleUtil

This is caused when the requested "output start time" is equal to or
larger than the last event time in a `Subtitle` object.

This resolves the error in Issue: androidx/media#1516, but subtitles are still not
renderered (probably because the timestamps aren't what we expect
somewhere, but I need to investigate this part further).

#cherrypick

PiperOrigin-RevId: 660462720
This commit is contained in:
ibaker 2024-08-07 11:14:04 -07:00 committed by Copybara-Service
parent 0530d663bc
commit 3763e5bc1d
3 changed files with 77 additions and 14 deletions

View File

@ -61,6 +61,10 @@
* TTML: Fix handling of percentage `tts:fontSize` values to ensure they
are correctly inherited from parent nodes with percentage `tts:fontSize`
values.
* Fix `IndexOutOfBoundsException` in `LegacySubtitleUtil` due to
incorrectly handling the case of the requested output start time being
greater than or equal to the final event time in the `Subtitle`
([#1516](https://github.com/androidx/media/issues/1516)).
* Metadata:
* Image:
* Add `ExternallyLoadedImageDecoder` for simplified integration with

View File

@ -37,17 +37,12 @@ public class LegacySubtitleUtil {
*/
public static void toCuesWithTiming(
Subtitle subtitle, OutputOptions outputOptions, Consumer<CuesWithTiming> output) {
if (subtitle.getEventTimeCount() == 0) {
return;
}
int startIndex = getStartIndex(subtitle, outputOptions);
int startIndex = getStartIndex(subtitle, outputOptions.startTimeUs);
boolean startedInMiddleOfCue = false;
if (outputOptions.startTimeUs != C.TIME_UNSET) {
if (outputOptions.startTimeUs != C.TIME_UNSET && startIndex < subtitle.getEventTimeCount()) {
List<Cue> cuesAtStartTime = subtitle.getCues(outputOptions.startTimeUs);
long firstEventTimeUs = subtitle.getEventTime(startIndex);
if (!cuesAtStartTime.isEmpty()
&& startIndex < subtitle.getEventTimeCount()
&& outputOptions.startTimeUs < firstEventTimeUs) {
if (!cuesAtStartTime.isEmpty() && outputOptions.startTimeUs < firstEventTimeUs) {
output.accept(
new CuesWithTiming(
cuesAtStartTime,
@ -74,16 +69,20 @@ public class LegacySubtitleUtil {
}
}
private static int getStartIndex(Subtitle subtitle, OutputOptions outputOptions) {
if (outputOptions.startTimeUs == C.TIME_UNSET) {
/**
* Returns the event index from {@code subtitle} that is equal to or after {@code startTimeUs}, or
* zero if {@code startTimeUs == C.TIME_UNSET}, or {@code subtitle.getEventTimeCount()} if {@code
* startTimeUs} is after all events in {@code subtitle}.
*/
private static int getStartIndex(Subtitle subtitle, long startTimeUs) {
if (startTimeUs == C.TIME_UNSET) {
return 0;
}
int nextEventTimeIndex = subtitle.getNextEventTimeIndex(outputOptions.startTimeUs);
int nextEventTimeIndex = subtitle.getNextEventTimeIndex(startTimeUs);
if (nextEventTimeIndex == C.INDEX_UNSET) {
return subtitle.getEventTimeCount();
nextEventTimeIndex = subtitle.getEventTimeCount();
}
if (nextEventTimeIndex > 0
&& subtitle.getEventTime(nextEventTimeIndex - 1) == outputOptions.startTimeUs) {
if (nextEventTimeIndex > 0 && subtitle.getEventTime(nextEventTimeIndex - 1) == startTimeUs) {
nextEventTimeIndex--;
}
return nextEventTimeIndex;

View File

@ -207,6 +207,24 @@ public class LegacySubtitleUtilWebvttTest {
.containsExactly(SECOND_SUBTITLE_STRING);
}
@Test
public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startAtSubtitleEnd_simpleSubtitle() {
ImmutableList<CuesWithTiming> cuesWithTimingsList =
toCuesWithTimingList(
SIMPLE_SUBTITLE, SubtitleParser.OutputOptions.onlyCuesAfter(4_000_000));
assertThat(cuesWithTimingsList).isEmpty();
}
@Test
public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startAfterSubtitleEnd_simpleSubtitle() {
ImmutableList<CuesWithTiming> cuesWithTimingsList =
toCuesWithTimingList(
SIMPLE_SUBTITLE, SubtitleParser.OutputOptions.onlyCuesAfter(4_500_000));
assertThat(cuesWithTimingsList).isEmpty();
}
@Test
public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startBetweenCues_consecutiveSubtitle() {
ImmutableList<CuesWithTiming> cuesWithTimingsList =
@ -340,6 +358,48 @@ public class LegacySubtitleUtilWebvttTest {
.containsExactly(FIRST_SUBTITLE_STRING);
}
@Test
public void
toCuesWithTiming_emitCuesAfterStartTimeThenThoseBefore_startAtSubtitleEnd_simpleSubtitle() {
ImmutableList<CuesWithTiming> cuesWithTimingsList =
toCuesWithTimingList(
SIMPLE_SUBTITLE,
SubtitleParser.OutputOptions.cuesAfterThenRemainingCuesBefore(4_000_000));
assertThat(cuesWithTimingsList).hasSize(2);
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(1_000_000);
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000);
assertThat(cuesWithTimingsList.get(0).endTimeUs).isEqualTo(2_000_000);
assertThat(cuesWithTimingsList.get(0).cues.stream().map(c -> c.text))
.containsExactly(FIRST_SUBTITLE_STRING);
assertThat(cuesWithTimingsList.get(1).startTimeUs).isEqualTo(3_000_000);
assertThat(cuesWithTimingsList.get(1).durationUs).isEqualTo(1_000_000);
assertThat(cuesWithTimingsList.get(1).endTimeUs).isEqualTo(4_000_000);
assertThat(cuesWithTimingsList.get(1).cues.stream().map(c -> c.text))
.containsExactly(SECOND_SUBTITLE_STRING);
}
@Test
public void
toCuesWithTiming_emitCuesAfterStartTimeThenThoseBefore_startAfterSubtitleEnd_simpleSubtitle() {
ImmutableList<CuesWithTiming> cuesWithTimingsList =
toCuesWithTimingList(
SIMPLE_SUBTITLE,
SubtitleParser.OutputOptions.cuesAfterThenRemainingCuesBefore(4_500_000));
assertThat(cuesWithTimingsList).hasSize(2);
assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(1_000_000);
assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000);
assertThat(cuesWithTimingsList.get(0).endTimeUs).isEqualTo(2_000_000);
assertThat(cuesWithTimingsList.get(0).cues.stream().map(c -> c.text))
.containsExactly(FIRST_SUBTITLE_STRING);
assertThat(cuesWithTimingsList.get(1).startTimeUs).isEqualTo(3_000_000);
assertThat(cuesWithTimingsList.get(1).durationUs).isEqualTo(1_000_000);
assertThat(cuesWithTimingsList.get(1).endTimeUs).isEqualTo(4_000_000);
assertThat(cuesWithTimingsList.get(1).cues.stream().map(c -> c.text))
.containsExactly(SECOND_SUBTITLE_STRING);
}
@Test
public void
toCuesWithTiming_emitCuesAfterStartTimeThenThoseBefore_startBetweenCues_consecutiveSubtitle() {