From b04b270c2baead1089db21b238a4243a58ecb1a2 Mon Sep 17 00:00:00 2001 From: ibaker Date: Tue, 20 Aug 2024 03:51:34 -0700 Subject: [PATCH] Use `SubtitleExtractor` from `MediaParserChunkExtractor` This uses a 'bundled' extractor (`SubtitleExtractor`) because `MediaParser` doesn't support transcoding subtitles to `application/x-media3-cues`. This transcoding is required to support non-legacy subtitle handling where they are parsed during extraction, instead of during rendering. PiperOrigin-RevId: 665282298 --- .../chunk/MediaParserChunkExtractor.java | 98 ++++++++++++++++--- 1 file changed, 84 insertions(+), 14 deletions(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/MediaParserChunkExtractor.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/MediaParserChunkExtractor.java index 8aed8adb9c..8467fdb3ac 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/MediaParserChunkExtractor.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/chunk/MediaParserChunkExtractor.java @@ -15,6 +15,7 @@ */ package androidx.media3.exoplayer.source.chunk; +import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.exoplayer.source.mediaparser.MediaParserUtil.PARAMETER_EAGERLY_EXPOSE_TRACK_TYPE; import static androidx.media3.exoplayer.source.mediaparser.MediaParserUtil.PARAMETER_EXPOSE_CAPTION_FORMATS; import static androidx.media3.exoplayer.source.mediaparser.MediaParserUtil.PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT; @@ -44,6 +45,10 @@ import androidx.media3.extractor.ExtractorInput; import androidx.media3.extractor.ExtractorOutput; import androidx.media3.extractor.SeekMap; import androidx.media3.extractor.TrackOutput; +import androidx.media3.extractor.text.DefaultSubtitleParserFactory; +import androidx.media3.extractor.text.SubtitleExtractor; +import androidx.media3.extractor.text.SubtitleParser; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -56,22 +61,87 @@ public final class MediaParserChunkExtractor implements ChunkExtractor { // Maximum TAG length is 23 characters. private static final String TAG = "MediaPrsrChunkExtractor"; - public static final ChunkExtractor.Factory FACTORY = - (primaryTrackType, - format, - enableEventMessageTrack, - closedCaptionFormats, - playerEmsgTrackOutput, - playerId) -> { - if (!MimeTypes.isText(format.containerMimeType)) { - // Container is either Matroska or Fragmented MP4. - return new MediaParserChunkExtractor( - primaryTrackType, format, closedCaptionFormats, playerId); - } else { - // This is a text track that does not require an extractor. + /** A {@link ChunkExtractor.Factory} for {@link MediaParserChunkExtractor} instances. */ + public static class Factory implements ChunkExtractor.Factory { + + private SubtitleParser.Factory subtitleParserFactory; + private boolean parseSubtitlesDuringExtraction; + + public Factory() { + subtitleParserFactory = new DefaultSubtitleParserFactory(); + } + + @CanIgnoreReturnValue + @Override + public Factory setSubtitleParserFactory(SubtitleParser.Factory subtitleParserFactory) { + this.subtitleParserFactory = checkNotNull(subtitleParserFactory); + return this; + } + + @CanIgnoreReturnValue + @Override + public Factory experimentalParseSubtitlesDuringExtraction( + boolean parseSubtitlesDuringExtraction) { + this.parseSubtitlesDuringExtraction = parseSubtitlesDuringExtraction; + return this; + } + + /** + * {@inheritDoc} + * + *

This implementation performs transcoding of the original format to {@link + * MimeTypes#APPLICATION_MEDIA3_CUES} if it is supported by {@link SubtitleParser.Factory}. + * + *

To modify the support behavior, you can {@linkplain + * #setSubtitleParserFactory(SubtitleParser.Factory) set your own subtitle parser factory}. + */ + @Override + public Format getOutputTextFormat(Format sourceFormat) { + if (parseSubtitlesDuringExtraction && subtitleParserFactory.supportsFormat(sourceFormat)) { + return sourceFormat + .buildUpon() + .setSampleMimeType(MimeTypes.APPLICATION_MEDIA3_CUES) + .setCueReplacementBehavior( + subtitleParserFactory.getCueReplacementBehavior(sourceFormat)) + .setCodecs( + sourceFormat.sampleMimeType + + (sourceFormat.codecs != null ? " " + sourceFormat.codecs : "")) + .setSubsampleOffsetUs(Format.OFFSET_SAMPLE_RELATIVE) + .build(); + } else { + return sourceFormat; + } + } + + @Nullable + @Override + public ChunkExtractor createProgressiveMediaExtractor( + @C.TrackType int primaryTrackType, + Format representationFormat, + boolean enableEventMessageTrack, + List closedCaptionFormats, + @Nullable TrackOutput playerEmsgTrackOutput, + PlayerId playerId) { + if (!MimeTypes.isText(representationFormat.containerMimeType)) { + // Container is either Matroska or Fragmented MP4. + return new MediaParserChunkExtractor( + primaryTrackType, representationFormat, closedCaptionFormats, playerId); + } else { + if (!parseSubtitlesDuringExtraction) { + // Subtitles will be parsed after decoding return null; + } else { + return new BundledChunkExtractor( + new SubtitleExtractor( + subtitleParserFactory.create(representationFormat), representationFormat), + primaryTrackType, + representationFormat); } - }; + } + } + } + + public static final ChunkExtractor.Factory FACTORY = new Factory(); private final OutputConsumerAdapterV30 outputConsumerAdapter; private final InputReaderAdapterV30 inputReaderAdapter;