diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/LegacySubtitleUtil.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/LegacySubtitleUtil.java new file mode 100644 index 0000000000..158c2958de --- /dev/null +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/LegacySubtitleUtil.java @@ -0,0 +1,74 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.extractor.text; + +import static java.lang.Math.max; + +import androidx.media3.common.C; +import androidx.media3.common.text.Cue; +import androidx.media3.common.util.Consumer; +import androidx.media3.common.util.UnstableApi; +import java.util.List; + +/** Utility methods for working with legacy {@link Subtitle} objects. */ +@UnstableApi +public class LegacySubtitleUtil { + + private LegacySubtitleUtil() {} + + /** + * Converts a {@link Subtitle} to a list of {@link CuesWithTiming} representing it, emitted to + * {@code output}. + * + *

This may only be called with {@link Subtitle} instances where the first event is non-empty + * and the last event is an empty cue list. + */ + public static void toCuesWithTiming( + Subtitle subtitle, + SubtitleParser.OutputOptions outputOptions, + Consumer output) { + int startIndex = + outputOptions.startTimeUs != C.TIME_UNSET + ? max(subtitle.getNextEventTimeIndex(outputOptions.startTimeUs) - 1, 0) + : 0; + for (int i = startIndex; i < subtitle.getEventTimeCount(); i++) { + outputSubtitleEvent(subtitle, i, output); + } + if (outputOptions.outputAllCues) { + for (int i = 0; i < startIndex; i++) { + outputSubtitleEvent(subtitle, i, output); + } + } + } + + private static void outputSubtitleEvent( + Subtitle subtitle, int eventIndex, Consumer output) { + long startTimeUs = subtitle.getEventTime(eventIndex); + List cuesForThisStartTime = subtitle.getCues(startTimeUs); + if (cuesForThisStartTime.isEmpty()) { + // An empty cue list has already been implicitly encoded in the duration of the previous + // sample. + return; + } else if (eventIndex == subtitle.getEventTimeCount() - 1) { + // The last cue list must be empty + throw new IllegalStateException(); + } + // It's safe to inspect element i+1, because we already exited the loop above if + // i == getEventTimeCount() - 1. + long durationUs = subtitle.getEventTime(eventIndex + 1) - subtitle.getEventTime(eventIndex); + output.accept(new CuesWithTiming(cuesForThisStartTime, startTimeUs, durationUs)); + } +} diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/Subtitle.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/Subtitle.java index 595fe6b36f..9ad0626e40 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/Subtitle.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/Subtitle.java @@ -18,7 +18,6 @@ package androidx.media3.extractor.text; import androidx.media3.common.C; import androidx.media3.common.text.Cue; import androidx.media3.common.util.UnstableApi; -import com.google.common.collect.ImmutableList; import java.util.List; /** A subtitle consisting of timed {@link Cue}s. */ @@ -57,24 +56,4 @@ public interface Subtitle { * @return A list of cues that should be displayed, possibly empty. */ List getCues(long timeUs); - - /** Converts the current instance to a list of {@link CuesWithTiming} representing it. */ - // TODO(b/181312195): Remove this when TtmlDecoder has been migrated to TtmlParser (and in-line it - // in DelegatingSubtitleDecoderTtmlParserTest). - default ImmutableList toCuesWithTimingList() { - ImmutableList.Builder allCues = ImmutableList.builder(); - for (int i = 0; i < getEventTimeCount(); i++) { - long startTimeUs = getEventTime(i); - List cuesForThisStartTime = getCues(startTimeUs); - if (cuesForThisStartTime.isEmpty() && i != 0) { - // An empty cue list has already been implicitly encoded in the duration of the previous - // sample (unless there was no previous sample). - continue; - } - long durationUs = - i < getEventTimeCount() - 1 ? getEventTime(i + 1) - getEventTime(i) : C.TIME_UNSET; - allCues.add(new CuesWithTiming(cuesForThisStartTime, startTimeUs, durationUs)); - } - return allCues.build(); - } } diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/webvtt/WebvttParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/webvtt/WebvttParser.java index 70c216e13b..62604398a7 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/webvtt/WebvttParser.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/webvtt/WebvttParser.java @@ -18,9 +18,11 @@ package androidx.media3.extractor.text.webvtt; import android.text.TextUtils; import androidx.annotation.Nullable; import androidx.media3.common.ParserException; +import androidx.media3.common.util.Consumer; import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.UnstableApi; import androidx.media3.extractor.text.CuesWithTiming; +import androidx.media3.extractor.text.LegacySubtitleUtil; import androidx.media3.extractor.text.SubtitleParser; import com.google.common.collect.ImmutableList; import java.util.ArrayList; @@ -53,6 +55,18 @@ public final class WebvttParser implements SubtitleParser { @Override public ImmutableList parse(byte[] data, int offset, int length) { + ImmutableList.Builder result = ImmutableList.builder(); + parse(data, offset, length, OutputOptions.allCues(), result::add); + return result.build(); + } + + @Override + public void parse( + byte[] data, + int offset, + int length, + OutputOptions outputOptions, + Consumer output) { parsableWebvttData.reset(data, /* limit= */ offset + length); parsableWebvttData.setPosition(offset); List definedStyles = new ArrayList<>(); @@ -85,7 +99,7 @@ public final class WebvttParser implements SubtitleParser { } } WebvttSubtitle subtitle = new WebvttSubtitle(cueInfos); - return subtitle.toCuesWithTimingList(); + LegacySubtitleUtil.toCuesWithTiming(subtitle, outputOptions, output); } /** diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/text/ttml/TtmlDecoderTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/text/ttml/TtmlDecoderTest.java index 02092ef714..41db1c79c8 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/text/ttml/TtmlDecoderTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/text/ttml/TtmlDecoderTest.java @@ -25,13 +25,11 @@ import androidx.media3.common.text.TextAnnotation; import androidx.media3.common.text.TextEmphasisSpan; import androidx.media3.common.util.Assertions; import androidx.media3.common.util.ColorParser; -import androidx.media3.extractor.text.CuesWithTiming; import androidx.media3.extractor.text.Subtitle; import androidx.media3.extractor.text.SubtitleDecoderException; import androidx.media3.test.utils.TestUtil; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.List; import org.junit.Test; @@ -877,39 +875,6 @@ public final class TtmlDecoderTest { assertThat(eighthCue.shearDegrees).isWithin(0.01f).of(90f); } - @Test - public void toCuesWithTimingConversion() throws IOException, SubtitleDecoderException { - TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE); - ImmutableList cuesWithTimingsList = subtitle.toCuesWithTimingList(); - - assertThat(subtitle.getEventTimeCount()).isEqualTo(4); - assertThat(subtitle.getCues(subtitle.getEventTime(1))).isEmpty(); - assertThat(subtitle.getCues(subtitle.getEventTime(3))).isEmpty(); - // cuesWithTimingsList has 2 fewer the events because it skips the empty cues - assertThat(cuesWithTimingsList).hasSize(2); - - subtitle = getSubtitle(INHERIT_STYLE_TTML_FILE); - cuesWithTimingsList = subtitle.toCuesWithTimingList(); - - assertThat(subtitle.getEventTimeCount()).isEqualTo(2); - assertThat(subtitle.getCues(subtitle.getEventTime(1))).isEmpty(); - // cuesWithTimingsList has 1 fewer events because it skips the empty cues - assertThat(cuesWithTimingsList).hasSize(1); - - subtitle = getSubtitle(INHERIT_MULTIPLE_STYLES_TTML_FILE); - cuesWithTimingsList = subtitle.toCuesWithTimingList(); - - assertThat(subtitle.getEventTimeCount()).isEqualTo(12); - assertThat(subtitle.getCues(subtitle.getEventTime(1))).isEmpty(); - assertThat(subtitle.getCues(subtitle.getEventTime(3))).isEmpty(); - assertThat(subtitle.getCues(subtitle.getEventTime(5))).isEmpty(); - assertThat(subtitle.getCues(subtitle.getEventTime(7))).isEmpty(); - assertThat(subtitle.getCues(subtitle.getEventTime(9))).isEmpty(); - assertThat(subtitle.getCues(subtitle.getEventTime(11))).isEmpty(); - // cuesWithTimingsList has 6 fewer events because it skips the empty cues - assertThat(cuesWithTimingsList).hasSize(6); - } - private static Spanned getOnlyCueTextAtTimeUs(Subtitle subtitle, long timeUs) { Cue cue = getOnlyCueAtTimeUs(subtitle, timeUs); assertThat(cue.text).isInstanceOf(Spanned.class); diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/text/webvtt/LegacySubtitleUtilWebvttTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/text/webvtt/LegacySubtitleUtilWebvttTest.java new file mode 100644 index 0000000000..6fed729f5c --- /dev/null +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/text/webvtt/LegacySubtitleUtilWebvttTest.java @@ -0,0 +1,221 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.extractor.text.webvtt; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.media3.extractor.text.CuesWithTiming; +import androidx.media3.extractor.text.LegacySubtitleUtil; +import androidx.media3.extractor.text.Subtitle; +import androidx.media3.extractor.text.SubtitleParser; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link LegacySubtitleUtil} using {@link WebvttSubtitle}. + * + *

This is in the webvtt package so we don't need to increase the visibility of {@link + * WebvttSubtitle}. + */ +@RunWith(AndroidJUnit4.class) +public class LegacySubtitleUtilWebvttTest { + + private static final String FIRST_SUBTITLE_STRING = "This is the first subtitle."; + private static final String SECOND_SUBTITLE_STRING = "This is the second subtitle."; + + private static final WebvttSubtitle simpleSubtitle = + new WebvttSubtitle( + Arrays.asList( + new WebvttCueInfo( + WebvttCueParser.newCueForText(FIRST_SUBTITLE_STRING), + /* startTimeUs= */ 1_000_000, + /* endTimeUs= */ 2_000_000), + new WebvttCueInfo( + WebvttCueParser.newCueForText(SECOND_SUBTITLE_STRING), + /* startTimeUs= */ 3_000_000, + /* endTimeUs= */ 4_000_000))); + + private static final WebvttSubtitle overlappingSubtitle = + new WebvttSubtitle( + Arrays.asList( + new WebvttCueInfo( + WebvttCueParser.newCueForText(FIRST_SUBTITLE_STRING), + /* startTimeUs= */ 1_000_000, + /* endTimeUs= */ 3_000_000), + new WebvttCueInfo( + WebvttCueParser.newCueForText(SECOND_SUBTITLE_STRING), + /* startTimeUs= */ 2_000_000, + /* endTimeUs= */ 4_000_000))); + + @Test + public void toCuesWithTiming_allCues_simpleSubtitle() { + ImmutableList cuesWithTimingsList = + toCuesWithTimingList(simpleSubtitle, SubtitleParser.OutputOptions.allCues()); + + assertThat(cuesWithTimingsList).hasSize(2); + assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(1_000_000); + assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000); + assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, 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(Lists.transform(cuesWithTimingsList.get(1).cues, c -> c.text)) + .containsExactly(SECOND_SUBTITLE_STRING); + } + + @Test + public void toCuesWithTiming_allCues_overlappingSubtitle() { + ImmutableList cuesWithTimingsList = + toCuesWithTimingList(overlappingSubtitle, SubtitleParser.OutputOptions.allCues()); + + assertThat(cuesWithTimingsList).hasSize(3); + assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(1_000_000); + assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000); + assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text)) + .containsExactly(FIRST_SUBTITLE_STRING); + assertThat(cuesWithTimingsList.get(1).startTimeUs).isEqualTo(2_000_000); + assertThat(cuesWithTimingsList.get(1).durationUs).isEqualTo(1_000_000); + assertThat(Lists.transform(cuesWithTimingsList.get(1).cues, c -> c.text)) + .containsExactly(FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING) + .inOrder(); + assertThat(cuesWithTimingsList.get(2).startTimeUs).isEqualTo(3_000_000); + assertThat(cuesWithTimingsList.get(2).durationUs).isEqualTo(1_000_000); + assertThat(Lists.transform(cuesWithTimingsList.get(2).cues, c -> c.text)) + .containsExactly(SECOND_SUBTITLE_STRING); + } + + @Test + public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startBetweenCues_simpleSubtitle() { + ImmutableList cuesWithTimingsList = + toCuesWithTimingList(simpleSubtitle, SubtitleParser.OutputOptions.onlyCuesAfter(2_500_000)); + + assertThat(cuesWithTimingsList).hasSize(1); + assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(3_000_000); + assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000); + assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text)) + .containsExactly(SECOND_SUBTITLE_STRING); + } + + @Test + public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startAtCueEnd_simpleSubtitle() { + ImmutableList cuesWithTimingsList = + toCuesWithTimingList(simpleSubtitle, SubtitleParser.OutputOptions.onlyCuesAfter(2_000_000)); + + assertThat(cuesWithTimingsList).hasSize(1); + assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(3_000_000); + assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000); + assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text)) + .containsExactly(SECOND_SUBTITLE_STRING); + } + + @Test + public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startAtCueStart_simpleSubtitle() { + ImmutableList cuesWithTimingsList = + toCuesWithTimingList(simpleSubtitle, SubtitleParser.OutputOptions.onlyCuesAfter(3_000_000)); + + assertThat(cuesWithTimingsList).hasSize(1); + assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(3_000_000); + assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000); + assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text)) + .containsExactly(SECOND_SUBTITLE_STRING); + } + + @Test + public void toCuesWithTiming_onlyEmitCuesAfterStartTime_startInMiddleOfCue_simpleSubtitle() { + ImmutableList cuesWithTimingsList = + toCuesWithTimingList(simpleSubtitle, SubtitleParser.OutputOptions.onlyCuesAfter(1_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(Lists.transform(cuesWithTimingsList.get(0).cues, 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(Lists.transform(cuesWithTimingsList.get(1).cues, c -> c.text)) + .containsExactly(SECOND_SUBTITLE_STRING); + } + + @Test + public void toCuesWithTiming_onlyEmitCuesAfterStartTime_overlappingSubtitle() { + ImmutableList cuesWithTimingsList = + toCuesWithTimingList( + overlappingSubtitle, SubtitleParser.OutputOptions.onlyCuesAfter(2_500_000)); + + assertThat(cuesWithTimingsList).hasSize(2); + assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(2_000_000); + assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000); + assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text)) + .containsExactly(FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING) + .inOrder(); + assertThat(cuesWithTimingsList.get(1).startTimeUs).isEqualTo(3_000_000); + assertThat(cuesWithTimingsList.get(1).durationUs).isEqualTo(1_000_000); + assertThat(Lists.transform(cuesWithTimingsList.get(1).cues, c -> c.text)) + .containsExactly(SECOND_SUBTITLE_STRING); + } + + @Test + public void toCuesWithTiming_emitCuesAfterStartTimeThenThoseBefore_simpleSubtitle() { + ImmutableList cuesWithTimingsList = + toCuesWithTimingList( + simpleSubtitle, + SubtitleParser.OutputOptions.cuesAfterThenRemainingCuesBefore(2_500_000)); + + assertThat(cuesWithTimingsList).hasSize(2); + assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(3_000_000); + assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000); + assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text)) + .containsExactly(SECOND_SUBTITLE_STRING); + assertThat(cuesWithTimingsList.get(1).startTimeUs).isEqualTo(1_000_000); + assertThat(cuesWithTimingsList.get(1).durationUs).isEqualTo(1_000_000); + assertThat(Lists.transform(cuesWithTimingsList.get(1).cues, c -> c.text)) + .containsExactly(FIRST_SUBTITLE_STRING); + } + + @Test + public void toCuesWithTiming_emitCuesAfterStartTimeThenThoseBefore_overlappingSubtitle() { + ImmutableList cuesWithTimingsList = + toCuesWithTimingList( + overlappingSubtitle, + SubtitleParser.OutputOptions.cuesAfterThenRemainingCuesBefore(2_500_000)); + + assertThat(cuesWithTimingsList).hasSize(3); + assertThat(cuesWithTimingsList.get(0).startTimeUs).isEqualTo(2_000_000); + assertThat(cuesWithTimingsList.get(0).durationUs).isEqualTo(1_000_000); + assertThat(Lists.transform(cuesWithTimingsList.get(0).cues, c -> c.text)) + .containsExactly(FIRST_SUBTITLE_STRING, SECOND_SUBTITLE_STRING) + .inOrder(); + assertThat(cuesWithTimingsList.get(1).startTimeUs).isEqualTo(3_000_000); + assertThat(cuesWithTimingsList.get(1).durationUs).isEqualTo(1_000_000); + assertThat(Lists.transform(cuesWithTimingsList.get(1).cues, c -> c.text)) + .containsExactly(SECOND_SUBTITLE_STRING); + assertThat(cuesWithTimingsList.get(2).startTimeUs).isEqualTo(1_000_000); + assertThat(cuesWithTimingsList.get(2).durationUs).isEqualTo(1_000_000); + assertThat(Lists.transform(cuesWithTimingsList.get(2).cues, c -> c.text)) + .containsExactly(FIRST_SUBTITLE_STRING); + } + + private static ImmutableList toCuesWithTimingList( + Subtitle subtitle, SubtitleParser.OutputOptions outputOptions) { + ImmutableList.Builder result = ImmutableList.builder(); + LegacySubtitleUtil.toCuesWithTiming(subtitle, outputOptions, result::add); + return result.build(); + } +} diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/text/webvtt/WebvttSubtitleTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/text/webvtt/WebvttSubtitleTest.java index adbd226606..4f22e3adfb 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/text/webvtt/WebvttSubtitleTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/text/webvtt/WebvttSubtitleTest.java @@ -20,9 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static java.lang.Long.MAX_VALUE; import androidx.media3.common.text.Cue; -import androidx.media3.extractor.text.CuesWithTiming; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -299,31 +297,6 @@ public class WebvttSubtitleTest { assertThat(nestedSubtitle.getCues(Long.MAX_VALUE)).isEmpty(); } - @Test - public void toCuesWithTimingConversion() { - ImmutableList cuesWithTimingsList = simpleSubtitle.toCuesWithTimingList(); - - assertThat(simpleSubtitle.getEventTimeCount()).isEqualTo(4); - assertThat(simpleSubtitle.getCues(simpleSubtitle.getEventTime(1))).isEmpty(); - assertThat(simpleSubtitle.getCues(simpleSubtitle.getEventTime(3))).isEmpty(); - // cuesWithTimingsList has half the events because it skips the empty cues - assertThat(cuesWithTimingsList).hasSize(2); - - cuesWithTimingsList = overlappingSubtitle.toCuesWithTimingList(); - - assertThat(overlappingSubtitle.getEventTimeCount()).isEqualTo(4); - assertThat(overlappingSubtitle.getCues(overlappingSubtitle.getEventTime(3))).isEmpty(); - // cuesWithTimingsList has one fewer events because it skips the empty cues - assertThat(cuesWithTimingsList).hasSize(3); - - cuesWithTimingsList = nestedSubtitle.toCuesWithTimingList(); - - assertThat(nestedSubtitle.getEventTimeCount()).isEqualTo(4); - assertThat(nestedSubtitle.getCues(nestedSubtitle.getEventTime(3))).isEmpty(); - // cuesWithTimingsList has one fewer events because it skips the empty cues - assertThat(cuesWithTimingsList).hasSize(3); - } - private static List getCueTexts(List cues) { List cueTexts = new ArrayList<>(); for (int i = 0; i < cues.size(); i++) {