diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/SubtitleParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/SubtitleParser.java
index 7ff9fcf28e..82a7262325 100644
--- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/SubtitleParser.java
+++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/SubtitleParser.java
@@ -16,21 +16,18 @@
package androidx.media3.extractor.text;
-import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.Format.CueReplacementBehavior;
import androidx.media3.common.util.Consumer;
import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
-import java.util.List;
/**
* Parses subtitle data into timed {@linkplain CuesWithTiming} instances.
*
- *
Instances are stateful, so samples can be fed in repeated calls to {@link #parse(byte[])}, and
- * one or more complete {@link CuesWithTiming} instances will be returned when enough data has been
+ *
Instances are stateful, so samples can be fed in repeated calls to {@link #parse}, and one or
+ * more complete {@link CuesWithTiming} instances will be returned when enough data has been
* received. Due to this stateful-ness, {@link #reset()} must be called after a seek or similar
* discontinuity in the source data.
*/
@@ -124,17 +121,6 @@ public interface SubtitleParser {
}
}
- /**
- * Parses {@code data} (and any data stored from previous invocations) and returns any resulting
- * complete {@link CuesWithTiming} instances.
- *
- *
Equivalent to {@link #parse(byte[], int, int) parse(data, 0, data.length)}.
- */
- @Nullable
- default List parse(byte[] data) {
- return parse(data, /* offset= */ 0, data.length);
- }
-
/**
* Parses {@code data} (and any data stored from previous invocations) and emits resulting {@link
* CuesWithTiming} instances.
@@ -146,33 +132,6 @@ public interface SubtitleParser {
parse(data, /* offset= */ 0, data.length, outputOptions, output);
}
- /**
- * Parses {@code data} (and any data stored from previous invocations) and returns any resulting
- * complete {@link CuesWithTiming} instances.
- *
- * Any samples not used from {@code data} will be persisted and used during subsequent calls to
- * this method.
- *
- *
{@link CuesWithTiming#startTimeUs} in the returned instance is derived only from the
- * provided sample data, so has to be considered together with any relevant {@link
- * Format#subsampleOffsetUs}. If the provided sample doesn't contain any timing information then
- * at most one {@link CuesWithTiming} instance will be returned, with {@link
- * CuesWithTiming#startTimeUs} set to {@link C#TIME_UNSET}, in which case {@link
- * Format#subsampleOffsetUs} must be {@link Format#OFFSET_SAMPLE_RELATIVE}.
- *
- * @param data The subtitle data to parse. This must contain only complete samples. For subtitles
- * muxed inside a media container, a sample is usually defined by the container. For subtitles
- * read from a text file, a sample is usually the entire contents of the text file.
- * @param offset The index in {@code data} to start reading from (inclusive).
- * @param length The number of bytes to read from {@code data}.
- * @return The {@linkplain CuesWithTiming} instances parsed from {@code data} (and possibly
- * previous provided samples too), sorted in ascending order by {@link
- * CuesWithTiming#startTimeUs}. Otherwise null if there is insufficient data to generate a
- * complete {@link CuesWithTiming}.
- */
- @Nullable
- List parse(byte[] data, int offset, int length);
-
/**
* Parses {@code data} (and any data stored from previous invocations) and emits any resulting
* complete {@link CuesWithTiming} instances via {@code output}.
@@ -197,35 +156,12 @@ public interface SubtitleParser {
* will be made on the thread that called this method, and will be completed before this
* method returns.
*/
- default void parse(
+ void parse(
byte[] data,
int offset,
int length,
OutputOptions outputOptions,
- Consumer output) {
- List cuesWithTimingList = parse(data, offset, length);
- if (cuesWithTimingList == null) {
- return;
- }
- @Nullable
- List cuesWithTimingBeforeRequestedStartTimeUs =
- outputOptions.startTimeUs != C.TIME_UNSET && outputOptions.outputAllCues
- ? new ArrayList<>()
- : null;
- for (CuesWithTiming cuesWithTiming : cuesWithTimingList) {
- if (outputOptions.startTimeUs == C.TIME_UNSET
- || cuesWithTiming.startTimeUs >= outputOptions.startTimeUs) {
- output.accept(cuesWithTiming);
- } else if (cuesWithTimingBeforeRequestedStartTimeUs != null) {
- cuesWithTimingBeforeRequestedStartTimeUs.add(cuesWithTiming);
- }
- }
- if (cuesWithTimingBeforeRequestedStartTimeUs != null) {
- for (CuesWithTiming cuesWithTiming : cuesWithTimingBeforeRequestedStartTimeUs) {
- output.accept(cuesWithTiming);
- }
- }
- }
+ Consumer output);
/**
* Parses {@code data} to a legacy {@link Subtitle} instance.
@@ -250,7 +186,7 @@ public interface SubtitleParser {
}
/**
- * Clears any data stored inside this parser from previous {@link #parse(byte[])} calls.
+ * Clears any data stored inside this parser from previous {@link #parse} calls.
*
* This must be called after a seek or other similar discontinuity in the source data.
*
diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/dvb/DvbParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/dvb/DvbParser.java
index d75b2d3299..4047830e97 100644
--- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/dvb/DvbParser.java
+++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/dvb/DvbParser.java
@@ -29,6 +29,7 @@ import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.Format.CueReplacementBehavior;
import androidx.media3.common.text.Cue;
+import androidx.media3.common.util.Consumer;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableBitArray;
import androidx.media3.common.util.ParsableByteArray;
@@ -143,9 +144,19 @@ public final class DvbParser implements SubtitleParser {
}
@Override
- public ImmutableList parse(byte[] data, int offset, int length) {
+ public void parse(
+ byte[] data,
+ int offset,
+ int length,
+ OutputOptions outputOptions,
+ Consumer output) {
ParsableBitArray dataBitArray = new ParsableBitArray(data, /* limit= */ offset + length);
dataBitArray.setPosition(offset);
+ output.accept(parse(dataBitArray));
+ }
+
+ private CuesWithTiming parse(ParsableBitArray dataBitArray) {
+
while (dataBitArray.bitsLeft() >= 48 // sync_byte (8) + segment header (40)
&& dataBitArray.readBits(8) == 0x0F) {
parseSubtitlingSegment(dataBitArray, subtitleService);
@@ -153,9 +164,8 @@ public final class DvbParser implements SubtitleParser {
@Nullable PageComposition pageComposition = subtitleService.pageComposition;
if (pageComposition == null) {
- return ImmutableList.of(
- new CuesWithTiming(
- ImmutableList.of(), /* startTimeUs= */ C.TIME_UNSET, /* durationUs= */ C.TIME_UNSET));
+ return new CuesWithTiming(
+ ImmutableList.of(), /* startTimeUs= */ C.TIME_UNSET, /* durationUs= */ C.TIME_UNSET);
}
// Update the canvas bitmap if necessary.
@@ -266,8 +276,8 @@ public final class DvbParser implements SubtitleParser {
canvas.restore();
}
- return ImmutableList.of(
- new CuesWithTiming(cues, /* startTimeUs= */ C.TIME_UNSET, /* durationUs= */ C.TIME_UNSET));
+ return new CuesWithTiming(
+ cues, /* startTimeUs= */ C.TIME_UNSET, /* durationUs= */ C.TIME_UNSET);
}
// Static parsing.
diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/pgs/PgsParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/pgs/PgsParser.java
index c916302a1b..87b4d4f6c7 100644
--- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/pgs/PgsParser.java
+++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/pgs/PgsParser.java
@@ -23,12 +23,12 @@ import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.Format.CueReplacementBehavior;
import androidx.media3.common.text.Cue;
+import androidx.media3.common.util.Consumer;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.extractor.text.CuesWithTiming;
import androidx.media3.extractor.text.SubtitleParser;
-import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.zip.Inflater;
@@ -68,7 +68,12 @@ public final class PgsParser implements SubtitleParser {
}
@Override
- public ImmutableList parse(byte[] data, int offset, int length) {
+ public void parse(
+ byte[] data,
+ int offset,
+ int length,
+ OutputOptions outputOptions,
+ Consumer output) {
buffer.reset(data, /* limit= */ offset + length);
buffer.setPosition(offset);
maybeInflateData(buffer);
@@ -80,7 +85,7 @@ public final class PgsParser implements SubtitleParser {
cues.add(cue);
}
}
- return ImmutableList.of(
+ output.accept(
new CuesWithTiming(cues, /* startTimeUs= */ C.TIME_UNSET, /* durationUs= */ C.TIME_UNSET));
}
diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaParser.java
index 5098b64f35..fc80730e72 100644
--- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaParser.java
+++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/ssa/SsaParser.java
@@ -32,6 +32,7 @@ import androidx.media3.common.Format;
import androidx.media3.common.Format.CueReplacementBehavior;
import androidx.media3.common.text.Cue;
import androidx.media3.common.util.Assertions;
+import androidx.media3.common.util.Consumer;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
@@ -40,7 +41,6 @@ import androidx.media3.extractor.text.CuesWithTiming;
import androidx.media3.extractor.text.SubtitleParser;
import com.google.common.base.Ascii;
import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableList;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -131,9 +131,13 @@ public final class SsaParser implements SubtitleParser {
return CUE_REPLACEMENT_BEHAVIOR;
}
- @Nullable
@Override
- public ImmutableList parse(byte[] data, int offset, int length) {
+ public void parse(
+ byte[] data,
+ int offset,
+ int length,
+ OutputOptions outputOptions,
+ Consumer output) {
List> cues = new ArrayList<>();
List startTimesUs = new ArrayList<>();
@@ -146,7 +150,11 @@ public final class SsaParser implements SubtitleParser {
}
parseEventBody(parsableByteArray, cues, startTimesUs, charset);
- ImmutableList.Builder cuesWithStartTimeAndDuration = ImmutableList.builder();
+ @Nullable
+ List cuesWithTimingBeforeRequestedStartTimeUs =
+ outputOptions.startTimeUs != C.TIME_UNSET && outputOptions.outputAllCues
+ ? new ArrayList<>()
+ : null;
for (int i = 0; i < cues.size(); i++) {
List cuesForThisStartTime = cues.get(i);
if (cuesForThisStartTime.isEmpty() && i != 0) {
@@ -160,10 +168,19 @@ public final class SsaParser implements SubtitleParser {
long startTimeUs = startTimesUs.get(i);
// It's safe to inspect element i+1, because we already exited the loop above if i=size()-1.
long durationUs = startTimesUs.get(i + 1) - startTimesUs.get(i);
- cuesWithStartTimeAndDuration.add(
- new CuesWithTiming(cuesForThisStartTime, startTimeUs, durationUs));
+ if (outputOptions.startTimeUs == C.TIME_UNSET || startTimeUs >= outputOptions.startTimeUs) {
+ output.accept(new CuesWithTiming(cuesForThisStartTime, startTimeUs, durationUs));
+
+ } else if (cuesWithTimingBeforeRequestedStartTimeUs != null) {
+ cuesWithTimingBeforeRequestedStartTimeUs.add(
+ new CuesWithTiming(cuesForThisStartTime, startTimeUs, durationUs));
+ }
+ }
+ if (cuesWithTimingBeforeRequestedStartTimeUs != null) {
+ for (CuesWithTiming cuesWithTiming : cuesWithTimingBeforeRequestedStartTimeUs) {
+ output.accept(cuesWithTiming);
+ }
}
- return cuesWithStartTimeAndDuration.build();
}
/**
diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/subrip/SubripParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/subrip/SubripParser.java
index 1ed05b83ba..c6794cbd37 100644
--- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/subrip/SubripParser.java
+++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/subrip/SubripParser.java
@@ -22,10 +22,12 @@ import android.text.Spanned;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.Format.CueReplacementBehavior;
import androidx.media3.common.text.Cue;
import androidx.media3.common.util.Assertions;
+import androidx.media3.common.util.Consumer;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
@@ -35,6 +37,7 @@ import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import java.nio.charset.Charset;
import java.util.ArrayList;
+import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -91,15 +94,22 @@ public final class SubripParser implements SubtitleParser {
return CUE_REPLACEMENT_BEHAVIOR;
}
- @Nullable
@Override
- public ImmutableList parse(byte[] data, int offset, int length) {
- ImmutableList.Builder cues = new ImmutableList.Builder<>();
-
+ public void parse(
+ byte[] data,
+ int offset,
+ int length,
+ OutputOptions outputOptions,
+ Consumer output) {
parsableByteArray.reset(data, /* limit= */ offset + length);
parsableByteArray.setPosition(offset);
Charset charset = detectUtfCharset(parsableByteArray);
+ @Nullable
+ List cuesWithTimingBeforeRequestedStartTimeUs =
+ outputOptions.startTimeUs != C.TIME_UNSET && outputOptions.outputAllCues
+ ? new ArrayList<>()
+ : null;
@Nullable String currentLine;
while ((currentLine = parsableByteArray.readLine(charset)) != null) {
if (currentLine.length() == 0) {
@@ -156,13 +166,25 @@ public final class SubripParser implements SubtitleParser {
break;
}
}
- cues.add(
- new CuesWithTiming(
- ImmutableList.of(buildCue(text, alignmentTag)),
- startTimeUs,
- /* durationUs= */ endTimeUs - startTimeUs));
+ if (outputOptions.startTimeUs == C.TIME_UNSET || startTimeUs >= outputOptions.startTimeUs) {
+ output.accept(
+ new CuesWithTiming(
+ ImmutableList.of(buildCue(text, alignmentTag)),
+ startTimeUs,
+ /* durationUs= */ endTimeUs - startTimeUs));
+ } else if (cuesWithTimingBeforeRequestedStartTimeUs != null) {
+ cuesWithTimingBeforeRequestedStartTimeUs.add(
+ new CuesWithTiming(
+ ImmutableList.of(buildCue(text, alignmentTag)),
+ startTimeUs,
+ /* durationUs= */ endTimeUs - startTimeUs));
+ }
+ }
+ if (cuesWithTimingBeforeRequestedStartTimeUs != null) {
+ for (CuesWithTiming cuesWithTiming : cuesWithTimingBeforeRequestedStartTimeUs) {
+ output.accept(cuesWithTiming);
+ }
}
- return cues.build();
}
/**
diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/ttml/TtmlParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/ttml/TtmlParser.java
index 5899680cc6..64b3a1b499 100644
--- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/ttml/TtmlParser.java
+++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/ttml/TtmlParser.java
@@ -41,7 +41,6 @@ import androidx.media3.extractor.text.Subtitle;
import androidx.media3.extractor.text.SubtitleDecoderException;
import androidx.media3.extractor.text.SubtitleParser;
import com.google.common.base.Ascii;
-import com.google.common.collect.ImmutableList;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayDeque;
@@ -133,13 +132,6 @@ public final class TtmlParser implements SubtitleParser {
return CUE_REPLACEMENT_BEHAVIOR;
}
- @Override
- public ImmutableList parse(byte[] data, int offset, int length) {
- ImmutableList.Builder cues = ImmutableList.builder();
- parse(data, offset, length, OutputOptions.allCues(), cues::add);
- return cues.build();
- }
-
@Override
public void parse(
byte[] data,
diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/tx3g/Tx3gParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/tx3g/Tx3gParser.java
index 3478693286..240c4617e7 100644
--- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/tx3g/Tx3gParser.java
+++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/tx3g/Tx3gParser.java
@@ -32,6 +32,7 @@ import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.Format.CueReplacementBehavior;
import androidx.media3.common.text.Cue;
+import androidx.media3.common.util.Consumer;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
@@ -137,16 +138,22 @@ public final class Tx3gParser implements SubtitleParser {
}
@Override
- public ImmutableList parse(byte[] data, int offset, int length) {
+ public void parse(
+ byte[] data,
+ int offset,
+ int length,
+ OutputOptions outputOptions,
+ Consumer output) {
parsableByteArray.reset(data, /* limit= */ offset + length);
parsableByteArray.setPosition(offset);
String cueTextString = readSubtitleText(parsableByteArray);
if (cueTextString.isEmpty()) {
- return ImmutableList.of(
+ output.accept(
new CuesWithTiming(
/* cues= */ ImmutableList.of(),
/* startTimeUs= */ C.TIME_UNSET,
/* durationUs= */ C.TIME_UNSET));
+ return;
}
// Attach default styles.
SpannableStringBuilder cueText = new SpannableStringBuilder(cueTextString);
@@ -180,7 +187,7 @@ public final class Tx3gParser implements SubtitleParser {
.setLine(verticalPlacement, LINE_TYPE_FRACTION)
.setLineAnchor(ANCHOR_TYPE_START)
.build();
- return ImmutableList.of(
+ output.accept(
new CuesWithTiming(
ImmutableList.of(cue),
/* startTimeUs= */ C.TIME_UNSET,
diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/text/webvtt/Mp4WebvttParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/text/webvtt/Mp4WebvttParser.java
index 6e46ad786d..bac014addd 100644
--- a/libraries/extractor/src/main/java/androidx/media3/extractor/text/webvtt/Mp4WebvttParser.java
+++ b/libraries/extractor/src/main/java/androidx/media3/extractor/text/webvtt/Mp4WebvttParser.java
@@ -22,12 +22,12 @@ import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.Format.CueReplacementBehavior;
import androidx.media3.common.text.Cue;
+import androidx.media3.common.util.Consumer;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.extractor.text.CuesWithTiming;
import androidx.media3.extractor.text.SubtitleParser;
-import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -67,7 +67,12 @@ public final class Mp4WebvttParser implements SubtitleParser {
}
@Override
- public ImmutableList parse(byte[] data, int offset, int length) {
+ public void parse(
+ byte[] data,
+ int offset,
+ int length,
+ OutputOptions outputOptions,
+ Consumer output) {
parsableByteArray.reset(data, /* limit= */ offset + length);
parsableByteArray.setPosition(offset);
List cues = new ArrayList<>();
@@ -86,7 +91,7 @@ public final class Mp4WebvttParser implements SubtitleParser {
parsableByteArray.skipBytes(boxSize - BOX_HEADER_SIZE);
}
}
- return ImmutableList.of(
+ output.accept(
new CuesWithTiming(cues, /* startTimeUs= */ C.TIME_UNSET, /* durationUs= */ C.TIME_UNSET));
}
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 c374f79b48..f08653a4c7 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
@@ -26,7 +26,6 @@ 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;
import java.util.List;
@@ -67,13 +66,6 @@ public final class WebvttParser implements SubtitleParser {
return CUE_REPLACEMENT_BEHAVIOR;
}
- @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,