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 * TTML: Fix handling of percentage `tts:fontSize` values to ensure they
are correctly inherited from parent nodes with percentage `tts:fontSize` are correctly inherited from parent nodes with percentage `tts:fontSize`
values. 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: * Metadata:
* Image: * Image:
* Add `ExternallyLoadedImageDecoder` for simplified integration with * Add `ExternallyLoadedImageDecoder` for simplified integration with

View File

@ -37,17 +37,12 @@ public class LegacySubtitleUtil {
*/ */
public static void toCuesWithTiming( public static void toCuesWithTiming(
Subtitle subtitle, OutputOptions outputOptions, Consumer<CuesWithTiming> output) { Subtitle subtitle, OutputOptions outputOptions, Consumer<CuesWithTiming> output) {
if (subtitle.getEventTimeCount() == 0) { int startIndex = getStartIndex(subtitle, outputOptions.startTimeUs);
return;
}
int startIndex = getStartIndex(subtitle, outputOptions);
boolean startedInMiddleOfCue = false; 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); List<Cue> cuesAtStartTime = subtitle.getCues(outputOptions.startTimeUs);
long firstEventTimeUs = subtitle.getEventTime(startIndex); long firstEventTimeUs = subtitle.getEventTime(startIndex);
if (!cuesAtStartTime.isEmpty() if (!cuesAtStartTime.isEmpty() && outputOptions.startTimeUs < firstEventTimeUs) {
&& startIndex < subtitle.getEventTimeCount()
&& outputOptions.startTimeUs < firstEventTimeUs) {
output.accept( output.accept(
new CuesWithTiming( new CuesWithTiming(
cuesAtStartTime, 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; return 0;
} }
int nextEventTimeIndex = subtitle.getNextEventTimeIndex(outputOptions.startTimeUs); int nextEventTimeIndex = subtitle.getNextEventTimeIndex(startTimeUs);
if (nextEventTimeIndex == C.INDEX_UNSET) { if (nextEventTimeIndex == C.INDEX_UNSET) {
return subtitle.getEventTimeCount(); nextEventTimeIndex = subtitle.getEventTimeCount();
} }
if (nextEventTimeIndex > 0 if (nextEventTimeIndex > 0 && subtitle.getEventTime(nextEventTimeIndex - 1) == startTimeUs) {
&& subtitle.getEventTime(nextEventTimeIndex - 1) == outputOptions.startTimeUs) {
nextEventTimeIndex--; nextEventTimeIndex--;
} }
return nextEventTimeIndex; return nextEventTimeIndex;

View File

@ -207,6 +207,24 @@ public class LegacySubtitleUtilWebvttTest {
.containsExactly(SECOND_SUBTITLE_STRING); .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 @Test
public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startBetweenCues_consecutiveSubtitle() { public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startBetweenCues_consecutiveSubtitle() {
ImmutableList<CuesWithTiming> cuesWithTimingsList = ImmutableList<CuesWithTiming> cuesWithTimingsList =
@ -340,6 +358,48 @@ public class LegacySubtitleUtilWebvttTest {
.containsExactly(FIRST_SUBTITLE_STRING); .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 @Test
public void public void
toCuesWithTiming_emitCuesAfterStartTimeThenThoseBefore_startBetweenCues_consecutiveSubtitle() { toCuesWithTiming_emitCuesAfterStartTimeThenThoseBefore_startBetweenCues_consecutiveSubtitle() {