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.mp3.Mp3Extractor;
|
||||||
import androidx.media3.extractor.mp4.FragmentedMp4Extractor;
|
import androidx.media3.extractor.mp4.FragmentedMp4Extractor;
|
||||||
import androidx.media3.extractor.text.SubtitleParser;
|
import androidx.media3.extractor.text.SubtitleParser;
|
||||||
import androidx.media3.extractor.text.SubtitleTranscodingExtractor;
|
|
||||||
import androidx.media3.extractor.ts.Ac3Extractor;
|
import androidx.media3.extractor.ts.Ac3Extractor;
|
||||||
import androidx.media3.extractor.ts.Ac4Extractor;
|
import androidx.media3.extractor.ts.Ac4Extractor;
|
||||||
import androidx.media3.extractor.ts.AdtsExtractor;
|
import androidx.media3.extractor.ts.AdtsExtractor;
|
||||||
@ -125,13 +124,19 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract
|
|||||||
Extractor newExtractorInstance;
|
Extractor newExtractorInstance;
|
||||||
// LINT.IfChange(extractor_instantiation)
|
// LINT.IfChange(extractor_instantiation)
|
||||||
if (extractor instanceof WebvttExtractor) {
|
if (extractor instanceof WebvttExtractor) {
|
||||||
newExtractorInstance =
|
SubtitleParser.Factory webvttSubtitleParserFactory = SubtitleParser.Factory.UNSUPPORTED;
|
||||||
new WebvttExtractor(multivariantPlaylistFormat.language, timestampAdjuster);
|
boolean parseSubtitlesDuringExtraction = false;
|
||||||
if (subtitleParserFactory != null
|
if (subtitleParserFactory != null
|
||||||
&& subtitleParserFactory.supportsFormat(multivariantPlaylistFormat)) {
|
&& subtitleParserFactory.supportsFormat(multivariantPlaylistFormat)) {
|
||||||
newExtractorInstance =
|
webvttSubtitleParserFactory = subtitleParserFactory;
|
||||||
new SubtitleTranscodingExtractor(newExtractorInstance, subtitleParserFactory);
|
parseSubtitlesDuringExtraction = true;
|
||||||
}
|
}
|
||||||
|
newExtractorInstance =
|
||||||
|
new WebvttExtractor(
|
||||||
|
multivariantPlaylistFormat.language,
|
||||||
|
timestampAdjuster,
|
||||||
|
webvttSubtitleParserFactory,
|
||||||
|
parseSubtitlesDuringExtraction);
|
||||||
} else if (extractor instanceof AdtsExtractor) {
|
} else if (extractor instanceof AdtsExtractor) {
|
||||||
newExtractorInstance = new AdtsExtractor();
|
newExtractorInstance = new AdtsExtractor();
|
||||||
} else if (extractor instanceof Ac3Extractor) {
|
} else if (extractor instanceof Ac3Extractor) {
|
||||||
|
@ -34,7 +34,6 @@ import androidx.media3.extractor.ExtractorInput;
|
|||||||
import androidx.media3.extractor.mp3.Mp3Extractor;
|
import androidx.media3.extractor.mp3.Mp3Extractor;
|
||||||
import androidx.media3.extractor.mp4.FragmentedMp4Extractor;
|
import androidx.media3.extractor.mp4.FragmentedMp4Extractor;
|
||||||
import androidx.media3.extractor.text.SubtitleParser;
|
import androidx.media3.extractor.text.SubtitleParser;
|
||||||
import androidx.media3.extractor.text.SubtitleTranscodingExtractor;
|
|
||||||
import androidx.media3.extractor.ts.Ac3Extractor;
|
import androidx.media3.extractor.ts.Ac3Extractor;
|
||||||
import androidx.media3.extractor.ts.Ac4Extractor;
|
import androidx.media3.extractor.ts.Ac4Extractor;
|
||||||
import androidx.media3.extractor.ts.AdtsExtractor;
|
import androidx.media3.extractor.ts.AdtsExtractor;
|
||||||
@ -188,12 +187,17 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
// LINT.IfChange(extractor_instantiation)
|
// LINT.IfChange(extractor_instantiation)
|
||||||
switch (fileType) {
|
switch (fileType) {
|
||||||
case FileTypes.WEBVTT:
|
case FileTypes.WEBVTT:
|
||||||
|
SubtitleParser.Factory webvttSubtitleParserFactory = SubtitleParser.Factory.UNSUPPORTED;
|
||||||
|
boolean parseSubtitlesDuringExtraction = false;
|
||||||
if (subtitleParserFactory != null && subtitleParserFactory.supportsFormat(format)) {
|
if (subtitleParserFactory != null && subtitleParserFactory.supportsFormat(format)) {
|
||||||
return new SubtitleTranscodingExtractor(
|
webvttSubtitleParserFactory = subtitleParserFactory;
|
||||||
new WebvttExtractor(format.language, timestampAdjuster), subtitleParserFactory);
|
parseSubtitlesDuringExtraction = true;
|
||||||
} else {
|
|
||||||
return new WebvttExtractor(format.language, timestampAdjuster);
|
|
||||||
}
|
}
|
||||||
|
return new WebvttExtractor(
|
||||||
|
format.language,
|
||||||
|
timestampAdjuster,
|
||||||
|
webvttSubtitleParserFactory,
|
||||||
|
parseSubtitlesDuringExtraction);
|
||||||
case FileTypes.ADTS:
|
case FileTypes.ADTS:
|
||||||
return new AdtsExtractor();
|
return new AdtsExtractor();
|
||||||
case FileTypes.AC3:
|
case FileTypes.AC3:
|
||||||
|
@ -31,6 +31,8 @@ import androidx.media3.extractor.ExtractorOutput;
|
|||||||
import androidx.media3.extractor.PositionHolder;
|
import androidx.media3.extractor.PositionHolder;
|
||||||
import androidx.media3.extractor.SeekMap;
|
import androidx.media3.extractor.SeekMap;
|
||||||
import androidx.media3.extractor.TrackOutput;
|
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 androidx.media3.extractor.text.webvtt.WebvttParserUtil;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -58,17 +60,38 @@ public final class WebvttExtractor implements Extractor {
|
|||||||
@Nullable private final String language;
|
@Nullable private final String language;
|
||||||
private final TimestampAdjuster timestampAdjuster;
|
private final TimestampAdjuster timestampAdjuster;
|
||||||
private final ParsableByteArray sampleDataWrapper;
|
private final ParsableByteArray sampleDataWrapper;
|
||||||
|
private final SubtitleParser.Factory subtitleParserFactory;
|
||||||
|
private final boolean parseSubtitlesDuringExtraction;
|
||||||
|
|
||||||
private @MonotonicNonNull ExtractorOutput output;
|
private @MonotonicNonNull ExtractorOutput output;
|
||||||
|
|
||||||
private byte[] sampleData;
|
private byte[] sampleData;
|
||||||
private int sampleSize;
|
private int sampleSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #WebvttExtractor(String, TimestampAdjuster, SubtitleParser.Factory,
|
||||||
|
* boolean)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public WebvttExtractor(@Nullable String language, TimestampAdjuster timestampAdjuster) {
|
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.language = language;
|
||||||
this.timestampAdjuster = timestampAdjuster;
|
this.timestampAdjuster = timestampAdjuster;
|
||||||
this.sampleDataWrapper = new ParsableByteArray();
|
this.sampleDataWrapper = new ParsableByteArray();
|
||||||
sampleData = new byte[1024];
|
sampleData = new byte[1024];
|
||||||
|
this.subtitleParserFactory = subtitleParserFactory;
|
||||||
|
this.parseSubtitlesDuringExtraction = parseSubtitlesDuringExtraction;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extractor implementation.
|
// Extractor implementation.
|
||||||
@ -94,7 +117,10 @@ public final class WebvttExtractor implements Extractor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput output) {
|
public void init(ExtractorOutput output) {
|
||||||
this.output = output;
|
this.output =
|
||||||
|
parseSubtitlesDuringExtraction
|
||||||
|
? new SubtitleTranscodingExtractorOutput(output, subtitleParserFactory)
|
||||||
|
: output;
|
||||||
output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
|
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.common.util.TimestampAdjuster;
|
||||||
import androidx.media3.extractor.ExtractorInput;
|
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.DumpFileAsserts;
|
||||||
import androidx.media3.test.utils.FakeExtractorInput;
|
import androidx.media3.test.utils.FakeExtractorInput;
|
||||||
import androidx.media3.test.utils.FakeExtractorOutput;
|
import androidx.media3.test.utils.FakeExtractorOutput;
|
||||||
@ -72,7 +74,12 @@ public class WebvttExtractorTest {
|
|||||||
TimestampAdjuster timestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0);
|
TimestampAdjuster timestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0);
|
||||||
// Prime the TimestampAdjuster with a close-ish timestamp (5s before the first cue).
|
// Prime the TimestampAdjuster with a close-ish timestamp (5s before the first cue).
|
||||||
timestampAdjuster.adjustTsTimestamp(384615190);
|
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
|
// We can't use ExtractorAsserts because WebvttExtractor doesn't fulfill the whole Extractor
|
||||||
// interface (e.g. throws an exception from seek()).
|
// interface (e.g. throws an exception from seek()).
|
||||||
FakeExtractorOutput output =
|
FakeExtractorOutput output =
|
||||||
@ -90,10 +97,42 @@ public class WebvttExtractorTest {
|
|||||||
"extractordumps/webvtt/with_x-timestamp-map_header.dump");
|
"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 {
|
private static boolean sniffData(byte[] data) throws IOException {
|
||||||
ExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
|
ExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
|
||||||
try {
|
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) {
|
} catch (EOFException e) {
|
||||||
return false;
|
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