Plumb SubtitleParser.Factory into WebvttExtractor
WebvttExtractor will no longer be wrapped in SubtitleTranscodingExtractor, but instead use SubtitleTranscodingExtractorOutput under the hood. A new constructor will take a boolean parameter to toggle between subtitle parsing during extraction (before the sample queue) or during decoding (after the sample queue). PiperOrigin-RevId: 597604942
This commit is contained in:
parent
f2be3fd0cb
commit
59afb4fb01
@ -29,7 +29,6 @@ import androidx.media3.extractor.PositionHolder;
|
||||
import androidx.media3.extractor.mp3.Mp3Extractor;
|
||||
import androidx.media3.extractor.mp4.FragmentedMp4Extractor;
|
||||
import androidx.media3.extractor.text.SubtitleParser;
|
||||
import androidx.media3.extractor.text.SubtitleTranscodingExtractor;
|
||||
import androidx.media3.extractor.ts.Ac3Extractor;
|
||||
import androidx.media3.extractor.ts.Ac4Extractor;
|
||||
import androidx.media3.extractor.ts.AdtsExtractor;
|
||||
@ -125,13 +124,19 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract
|
||||
Extractor newExtractorInstance;
|
||||
// LINT.IfChange(extractor_instantiation)
|
||||
if (extractor instanceof WebvttExtractor) {
|
||||
newExtractorInstance =
|
||||
new WebvttExtractor(multivariantPlaylistFormat.language, timestampAdjuster);
|
||||
SubtitleParser.Factory webvttSubtitleParserFactory = SubtitleParser.Factory.UNSUPPORTED;
|
||||
boolean parseSubtitlesDuringExtraction = false;
|
||||
if (subtitleParserFactory != null
|
||||
&& subtitleParserFactory.supportsFormat(multivariantPlaylistFormat)) {
|
||||
newExtractorInstance =
|
||||
new SubtitleTranscodingExtractor(newExtractorInstance, subtitleParserFactory);
|
||||
webvttSubtitleParserFactory = subtitleParserFactory;
|
||||
parseSubtitlesDuringExtraction = true;
|
||||
}
|
||||
newExtractorInstance =
|
||||
new WebvttExtractor(
|
||||
multivariantPlaylistFormat.language,
|
||||
timestampAdjuster,
|
||||
webvttSubtitleParserFactory,
|
||||
parseSubtitlesDuringExtraction);
|
||||
} else if (extractor instanceof AdtsExtractor) {
|
||||
newExtractorInstance = new AdtsExtractor();
|
||||
} else if (extractor instanceof Ac3Extractor) {
|
||||
|
@ -34,7 +34,6 @@ import androidx.media3.extractor.ExtractorInput;
|
||||
import androidx.media3.extractor.mp3.Mp3Extractor;
|
||||
import androidx.media3.extractor.mp4.FragmentedMp4Extractor;
|
||||
import androidx.media3.extractor.text.SubtitleParser;
|
||||
import androidx.media3.extractor.text.SubtitleTranscodingExtractor;
|
||||
import androidx.media3.extractor.ts.Ac3Extractor;
|
||||
import androidx.media3.extractor.ts.Ac4Extractor;
|
||||
import androidx.media3.extractor.ts.AdtsExtractor;
|
||||
@ -188,12 +187,17 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
||||
// LINT.IfChange(extractor_instantiation)
|
||||
switch (fileType) {
|
||||
case FileTypes.WEBVTT:
|
||||
SubtitleParser.Factory webvttSubtitleParserFactory = SubtitleParser.Factory.UNSUPPORTED;
|
||||
boolean parseSubtitlesDuringExtraction = false;
|
||||
if (subtitleParserFactory != null && subtitleParserFactory.supportsFormat(format)) {
|
||||
return new SubtitleTranscodingExtractor(
|
||||
new WebvttExtractor(format.language, timestampAdjuster), subtitleParserFactory);
|
||||
} else {
|
||||
return new WebvttExtractor(format.language, timestampAdjuster);
|
||||
webvttSubtitleParserFactory = subtitleParserFactory;
|
||||
parseSubtitlesDuringExtraction = true;
|
||||
}
|
||||
return new WebvttExtractor(
|
||||
format.language,
|
||||
timestampAdjuster,
|
||||
webvttSubtitleParserFactory,
|
||||
parseSubtitlesDuringExtraction);
|
||||
case FileTypes.ADTS:
|
||||
return new AdtsExtractor();
|
||||
case FileTypes.AC3:
|
||||
|
@ -31,6 +31,8 @@ import androidx.media3.extractor.ExtractorOutput;
|
||||
import androidx.media3.extractor.PositionHolder;
|
||||
import androidx.media3.extractor.SeekMap;
|
||||
import androidx.media3.extractor.TrackOutput;
|
||||
import androidx.media3.extractor.text.SubtitleParser;
|
||||
import androidx.media3.extractor.text.SubtitleTranscodingExtractorOutput;
|
||||
import androidx.media3.extractor.text.webvtt.WebvttParserUtil;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
@ -58,17 +60,38 @@ public final class WebvttExtractor implements Extractor {
|
||||
@Nullable private final String language;
|
||||
private final TimestampAdjuster timestampAdjuster;
|
||||
private final ParsableByteArray sampleDataWrapper;
|
||||
private final SubtitleParser.Factory subtitleParserFactory;
|
||||
private final boolean parseSubtitlesDuringExtraction;
|
||||
|
||||
private @MonotonicNonNull ExtractorOutput output;
|
||||
|
||||
private byte[] sampleData;
|
||||
private int sampleSize;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #WebvttExtractor(String, TimestampAdjuster, SubtitleParser.Factory,
|
||||
* boolean)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public WebvttExtractor(@Nullable String language, TimestampAdjuster timestampAdjuster) {
|
||||
this(
|
||||
language,
|
||||
timestampAdjuster,
|
||||
SubtitleParser.Factory.UNSUPPORTED,
|
||||
/* parseSubtitlesDuringExtraction= */ false);
|
||||
}
|
||||
|
||||
public WebvttExtractor(
|
||||
@Nullable String language,
|
||||
TimestampAdjuster timestampAdjuster,
|
||||
SubtitleParser.Factory subtitleParserFactory,
|
||||
boolean parseSubtitlesDuringExtraction) {
|
||||
this.language = language;
|
||||
this.timestampAdjuster = timestampAdjuster;
|
||||
this.sampleDataWrapper = new ParsableByteArray();
|
||||
sampleData = new byte[1024];
|
||||
this.subtitleParserFactory = subtitleParserFactory;
|
||||
this.parseSubtitlesDuringExtraction = parseSubtitlesDuringExtraction;
|
||||
}
|
||||
|
||||
// Extractor implementation.
|
||||
@ -94,7 +117,10 @@ public final class WebvttExtractor implements Extractor {
|
||||
|
||||
@Override
|
||||
public void init(ExtractorOutput output) {
|
||||
this.output = output;
|
||||
this.output =
|
||||
parseSubtitlesDuringExtraction
|
||||
? new SubtitleTranscodingExtractorOutput(output, subtitleParserFactory)
|
||||
: output;
|
||||
output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.media3.common.util.TimestampAdjuster;
|
||||
import androidx.media3.extractor.ExtractorInput;
|
||||
import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
|
||||
import androidx.media3.extractor.text.SubtitleParser;
|
||||
import androidx.media3.test.utils.DumpFileAsserts;
|
||||
import androidx.media3.test.utils.FakeExtractorInput;
|
||||
import androidx.media3.test.utils.FakeExtractorOutput;
|
||||
@ -72,7 +74,12 @@ public class WebvttExtractorTest {
|
||||
TimestampAdjuster timestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0);
|
||||
// Prime the TimestampAdjuster with a close-ish timestamp (5s before the first cue).
|
||||
timestampAdjuster.adjustTsTimestamp(384615190);
|
||||
WebvttExtractor extractor = new WebvttExtractor(/* language= */ null, timestampAdjuster);
|
||||
WebvttExtractor extractor =
|
||||
new WebvttExtractor(
|
||||
/* language= */ null,
|
||||
timestampAdjuster,
|
||||
SubtitleParser.Factory.UNSUPPORTED,
|
||||
/* parseSubtitlesDuringExtraction= */ false);
|
||||
// We can't use ExtractorAsserts because WebvttExtractor doesn't fulfill the whole Extractor
|
||||
// interface (e.g. throws an exception from seek()).
|
||||
FakeExtractorOutput output =
|
||||
@ -90,10 +97,42 @@ public class WebvttExtractorTest {
|
||||
"extractordumps/webvtt/with_x-timestamp-map_header.dump");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void read_handlesLargeCueTimestamps_withSubtitleParsingDuringExtraction()
|
||||
throws Exception {
|
||||
TimestampAdjuster timestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0);
|
||||
// Prime the TimestampAdjuster with a close-ish timestamp (5s before the first cue).
|
||||
timestampAdjuster.adjustTsTimestamp(384615190);
|
||||
WebvttExtractor extractor =
|
||||
new WebvttExtractor(
|
||||
/* language= */ null,
|
||||
timestampAdjuster,
|
||||
new DefaultSubtitleParserFactory(),
|
||||
/* parseSubtitlesDuringExtraction= */ true);
|
||||
|
||||
FakeExtractorOutput output =
|
||||
TestUtil.extractAllSamplesFromFile(
|
||||
extractor,
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
"media/webvtt/with_x-timestamp-map_header");
|
||||
|
||||
// There are 2 cues in the file which are fed into 2 different samples during extraction
|
||||
// This is different to the parsing-during-decoding flow where the whole file becomes a sample
|
||||
DumpFileAsserts.assertOutput(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
output,
|
||||
"extractordumps/webvtt/with_x-timestamp-map_header_parsed_during_extraction.dump");
|
||||
}
|
||||
|
||||
private static boolean sniffData(byte[] data) throws IOException {
|
||||
ExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
|
||||
try {
|
||||
return new WebvttExtractor(/* language= */ null, new TimestampAdjuster(0)).sniff(input);
|
||||
return new WebvttExtractor(
|
||||
/* language= */ null,
|
||||
new TimestampAdjuster(0),
|
||||
SubtitleParser.Factory.UNSUPPORTED,
|
||||
/* parseSubtitlesDuringExtraction= */ false)
|
||||
.sniff(input);
|
||||
} catch (EOFException e) {
|
||||
return false;
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
total output bytes = 2248
|
||||
sample count = 2
|
||||
format 0:
|
||||
sampleMimeType = application/x-media3-cues
|
||||
codecs = text/vtt
|
||||
sample 0:
|
||||
time = 5000155
|
||||
flags = 1
|
||||
data = length 1124, hash E87709B3
|
||||
sample 1:
|
||||
time = 6754155
|
||||
flags = 1
|
||||
data = length 1124, hash 5C8A8288
|
||||
tracksEnded = true
|
Loading…
x
Reference in New Issue
Block a user