Add setters of SubtitleParser.Factory and experimental toggle

The `SubtitleParser.Factory` is no longer @Nullable and the experimenting toggle is used to enable/disable the use of this factory for subtitle parsing during extraction.

The three places that will hold the "truth" for the `SubtitleParser.Factory` are: BundledChunkExtractor.Factory, SsChunkSource.Factory, DefaultHlsExtractorFactory

DASH: `DashMediaSource.Factory` would only propagate it to `DashChunkSource.Factory` -> `BundledChunkExtractor.Factory`

SS: `SSMediaSource.Factory` -> `SsChunkSource.Factory`

HLS: `HlsMediaSource.Factory` -> `HlsExtractorFactory`

#minor-release

PiperOrigin-RevId: 601151615
This commit is contained in:
jbibik 2024-01-24 09:52:48 -08:00 committed by Copybara-Service
parent 0acf6902e5
commit 4d7b23f0d1
15 changed files with 297 additions and 220 deletions

View File

@ -15,6 +15,11 @@
implementing a custom `CompositeSequenceableLoaderFactory`. implementing a custom `CompositeSequenceableLoaderFactory`.
* Fix issue where repeating the same time causes metadata from this item * Fix issue where repeating the same time causes metadata from this item
to be cleared ([#1007](https://github.com/androidx/media/issues/1007)). to be cleared ([#1007](https://github.com/androidx/media/issues/1007)).
* Rename `experimentalSetSubtitleParserFactory` methods on
`BundledChunkExtractor.Factory` and `DefaultHlsExtractorFactory` to
`setSubtitleParserFactory` and disallow passing `null`. Use the new
`experimentalParseSubtitlesDuringExtraction(boolean)` methods to control
parsing behaviour.
* Transformer: * Transformer:
* Track Selection: * Track Selection:
* Extractors: * Extractors:

View File

@ -15,6 +15,7 @@
*/ */
package androidx.media3.exoplayer.source.chunk; package androidx.media3.exoplayer.source.chunk;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.castNonNull; import static androidx.media3.common.util.Util.castNonNull;
import android.util.SparseArray; import android.util.SparseArray;
@ -39,9 +40,11 @@ import androidx.media3.extractor.jpeg.JpegExtractor;
import androidx.media3.extractor.mkv.MatroskaExtractor; import androidx.media3.extractor.mkv.MatroskaExtractor;
import androidx.media3.extractor.mp4.FragmentedMp4Extractor; import androidx.media3.extractor.mp4.FragmentedMp4Extractor;
import androidx.media3.extractor.png.PngExtractor; import androidx.media3.extractor.png.PngExtractor;
import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
import androidx.media3.extractor.text.SubtitleExtractor; import androidx.media3.extractor.text.SubtitleExtractor;
import androidx.media3.extractor.text.SubtitleParser; import androidx.media3.extractor.text.SubtitleParser;
import androidx.media3.extractor.text.SubtitleTranscodingExtractor; import androidx.media3.extractor.text.SubtitleTranscodingExtractor;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -57,24 +60,25 @@ public final class BundledChunkExtractor implements ExtractorOutput, ChunkExtrac
/** {@link ChunkExtractor.Factory} for {@link BundledChunkExtractor}. */ /** {@link ChunkExtractor.Factory} for {@link BundledChunkExtractor}. */
public static final class Factory implements ChunkExtractor.Factory { public static final class Factory implements ChunkExtractor.Factory {
/** Non-null if subtitles should be parsed during extraction, null otherwise. */ private SubtitleParser.Factory subtitleParserFactory;
@Nullable private SubtitleParser.Factory subtitleParserFactory; private boolean parseSubtitlesDuringExtraction;
/** public Factory() {
* Sets the {@link SubtitleParser.Factory} to use for parsing subtitles during extraction, or subtitleParserFactory = new DefaultSubtitleParserFactory();
* null to parse subtitles during decoding. The default is null (subtitles parsed after }
* decoding).
* @CanIgnoreReturnValue
* <p>This method is experimental. Its default value may change, or it may be renamed or removed @Override
* in a future release. public Factory setSubtitleParserFactory(SubtitleParser.Factory subtitleParserFactory) {
* this.subtitleParserFactory = checkNotNull(subtitleParserFactory);
* @param subtitleParserFactory The {@link SubtitleParser.Factory} for parsing subtitles during return this;
* extraction. }
* @return This factory, for convenience.
*/ @CanIgnoreReturnValue
public Factory experimentalSetSubtitleParserFactory( @Override
@Nullable SubtitleParser.Factory subtitleParserFactory) { public Factory experimentalParseSubtitlesDuringExtraction(
this.subtitleParserFactory = subtitleParserFactory; boolean parseSubtitlesDuringExtraction) {
this.parseSubtitlesDuringExtraction = parseSubtitlesDuringExtraction;
return this; return this;
} }
@ -85,18 +89,16 @@ public final class BundledChunkExtractor implements ExtractorOutput, ChunkExtrac
* MimeTypes#APPLICATION_MEDIA3_CUES} if it is supported by {@link SubtitleParser.Factory}. * MimeTypes#APPLICATION_MEDIA3_CUES} if it is supported by {@link SubtitleParser.Factory}.
* *
* <p>To modify the support behavior, you can {@linkplain * <p>To modify the support behavior, you can {@linkplain
* #experimentalSetSubtitleParserFactory(SubtitleParser.Factory) set your own subtitle parser * #setSubtitleParserFactory(SubtitleParser.Factory) set your own subtitle parser factory}.
* factory}.
*/ */
@Override @Override
public Format getOutputTextFormat(Format sourceFormat) { public Format getOutputTextFormat(Format sourceFormat) {
if (subtitleParserFactory != null && subtitleParserFactory.supportsFormat(sourceFormat)) { if (parseSubtitlesDuringExtraction && subtitleParserFactory.supportsFormat(sourceFormat)) {
@Format.CueReplacementBehavior
int cueReplacementBehavior = subtitleParserFactory.getCueReplacementBehavior(sourceFormat);
return sourceFormat return sourceFormat
.buildUpon() .buildUpon()
.setSampleMimeType(MimeTypes.APPLICATION_MEDIA3_CUES) .setSampleMimeType(MimeTypes.APPLICATION_MEDIA3_CUES)
.setCueReplacementBehavior(cueReplacementBehavior) .setCueReplacementBehavior(
subtitleParserFactory.getCueReplacementBehavior(sourceFormat))
.setCodecs( .setCodecs(
sourceFormat.sampleMimeType sourceFormat.sampleMimeType
+ (sourceFormat.codecs != null ? " " + sourceFormat.codecs : "")) + (sourceFormat.codecs != null ? " " + sourceFormat.codecs : ""))
@ -119,7 +121,7 @@ public final class BundledChunkExtractor implements ExtractorOutput, ChunkExtrac
@Nullable String containerMimeType = representationFormat.containerMimeType; @Nullable String containerMimeType = representationFormat.containerMimeType;
Extractor extractor; Extractor extractor;
if (MimeTypes.isText(containerMimeType)) { if (MimeTypes.isText(containerMimeType)) {
if (subtitleParserFactory == null) { if (!parseSubtitlesDuringExtraction) {
// Subtitles will be parsed after decoding // Subtitles will be parsed after decoding
return null; return null;
} else { } else {
@ -129,15 +131,10 @@ public final class BundledChunkExtractor implements ExtractorOutput, ChunkExtrac
} }
} else if (MimeTypes.isMatroska(containerMimeType)) { } else if (MimeTypes.isMatroska(containerMimeType)) {
@MatroskaExtractor.Flags int flags = MatroskaExtractor.FLAG_DISABLE_SEEK_FOR_CUES; @MatroskaExtractor.Flags int flags = MatroskaExtractor.FLAG_DISABLE_SEEK_FOR_CUES;
if (subtitleParserFactory == null) { if (!parseSubtitlesDuringExtraction) {
flags |= MatroskaExtractor.FLAG_EMIT_RAW_SUBTITLE_DATA; flags |= MatroskaExtractor.FLAG_EMIT_RAW_SUBTITLE_DATA;
} }
extractor = extractor = new MatroskaExtractor(subtitleParserFactory, flags);
new MatroskaExtractor(
subtitleParserFactory != null
? subtitleParserFactory
: SubtitleParser.Factory.UNSUPPORTED,
flags);
} else if (Objects.equals(containerMimeType, MimeTypes.IMAGE_JPEG)) { } else if (Objects.equals(containerMimeType, MimeTypes.IMAGE_JPEG)) {
extractor = new JpegExtractor(JpegExtractor.FLAG_READ_IMAGE); extractor = new JpegExtractor(JpegExtractor.FLAG_READ_IMAGE);
} else if (Objects.equals(containerMimeType, MimeTypes.IMAGE_PNG)) { } else if (Objects.equals(containerMimeType, MimeTypes.IMAGE_PNG)) {
@ -147,21 +144,19 @@ public final class BundledChunkExtractor implements ExtractorOutput, ChunkExtrac
if (enableEventMessageTrack) { if (enableEventMessageTrack) {
flags |= FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK; flags |= FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK;
} }
if (subtitleParserFactory == null) { if (!parseSubtitlesDuringExtraction) {
flags |= FragmentedMp4Extractor.FLAG_EMIT_RAW_SUBTITLE_DATA; flags |= FragmentedMp4Extractor.FLAG_EMIT_RAW_SUBTITLE_DATA;
} }
extractor = extractor =
new FragmentedMp4Extractor( new FragmentedMp4Extractor(
subtitleParserFactory != null subtitleParserFactory,
? subtitleParserFactory
: SubtitleParser.Factory.UNSUPPORTED,
flags, flags,
/* timestampAdjuster= */ null, /* timestampAdjuster= */ null,
/* sideloadedTrack= */ null, /* sideloadedTrack= */ null,
closedCaptionFormats, closedCaptionFormats,
playerEmsgTrackOutput); playerEmsgTrackOutput);
} }
if (subtitleParserFactory != null if (parseSubtitlesDuringExtraction
&& !MimeTypes.isText(containerMimeType) && !MimeTypes.isText(containerMimeType)
&& !(extractor.getUnderlyingImplementation() instanceof FragmentedMp4Extractor) && !(extractor.getUnderlyingImplementation() instanceof FragmentedMp4Extractor)
&& !(extractor.getUnderlyingImplementation() instanceof MatroskaExtractor)) { && !(extractor.getUnderlyingImplementation() instanceof MatroskaExtractor)) {

View File

@ -25,6 +25,8 @@ import androidx.media3.extractor.ChunkIndex;
import androidx.media3.extractor.Extractor; import androidx.media3.extractor.Extractor;
import androidx.media3.extractor.ExtractorInput; import androidx.media3.extractor.ExtractorInput;
import androidx.media3.extractor.TrackOutput; import androidx.media3.extractor.TrackOutput;
import androidx.media3.extractor.text.SubtitleParser;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -41,24 +43,34 @@ public interface ChunkExtractor {
interface Factory { interface Factory {
/** /**
* Returns a new {@link ChunkExtractor} instance. * Sets the {@link SubtitleParser.Factory} to use for parsing subtitles during extraction. The
* default factory value is implementation dependent.
* *
* @param primaryTrackType The {@link C.TrackType type} of the primary track. * @param subtitleParserFactory The {@link SubtitleParser.Factory} for parsing subtitles during
* @param representationFormat The format of the representation to extract from. * extraction.
* @param enableEventMessageTrack Whether to enable the event message track. * @return This factory, for convenience.
* @param closedCaptionFormats The {@link Format Formats} of the Closed-Caption tracks.
* @param playerEmsgTrackOutput The {@link TrackOutput} for extracted EMSG messages, or null.
* @param playerId The {@link PlayerId} of the player using this chunk extractor.
* @return A new {@link ChunkExtractor} instance, or null if not applicable.
*/ */
@Nullable @CanIgnoreReturnValue
ChunkExtractor createProgressiveMediaExtractor( default Factory setSubtitleParserFactory(SubtitleParser.Factory subtitleParserFactory) {
@C.TrackType int primaryTrackType, return this;
Format representationFormat, }
boolean enableEventMessageTrack,
List<Format> closedCaptionFormats, /**
@Nullable TrackOutput playerEmsgTrackOutput, * Sets whether subtitles should be parsed as part of extraction (before being added to the
PlayerId playerId); * sample queue) or as part of rendering (when being taken from the sample queue). Defaults to
* {@code false} (i.e. subtitles will be parsed as part of rendering).
*
* <p>This method is experimental and will be renamed or removed in a future release.
*
* @param parseSubtitlesDuringExtraction Whether to parse subtitles during extraction or
* rendering.
* @return This factory, for convenience.
*/
@CanIgnoreReturnValue
default Factory experimentalParseSubtitlesDuringExtraction(
boolean parseSubtitlesDuringExtraction) {
return this;
}
/** /**
* Returns the output {@link Format} of emitted {@linkplain C#TRACK_TYPE_TEXT text samples} * Returns the output {@link Format} of emitted {@linkplain C#TRACK_TYPE_TEXT text samples}
@ -79,6 +91,26 @@ public interface ChunkExtractor {
default Format getOutputTextFormat(Format sourceFormat) { default Format getOutputTextFormat(Format sourceFormat) {
return sourceFormat; return sourceFormat;
} }
/**
* Returns a new {@link ChunkExtractor} instance.
*
* @param primaryTrackType The {@link C.TrackType type} of the primary track.
* @param representationFormat The format of the representation to extract from.
* @param enableEventMessageTrack Whether to enable the event message track.
* @param closedCaptionFormats The {@link Format Formats} of the Closed-Caption tracks.
* @param playerEmsgTrackOutput The {@link TrackOutput} for extracted EMSG messages, or null.
* @param playerId The {@link PlayerId} of the player using this chunk extractor.
* @return A new {@link ChunkExtractor} instance, or null if not applicable.
*/
@Nullable
ChunkExtractor createProgressiveMediaExtractor(
@C.TrackType int primaryTrackType,
Format representationFormat,
boolean enableEventMessageTrack,
List<Format> closedCaptionFormats,
@Nullable TrackOutput playerEmsgTrackOutput,
PlayerId playerId);
} }
/** Provides {@link TrackOutput} instances to be written to during extraction. */ /** Provides {@link TrackOutput} instances to be written to during extraction. */

View File

@ -30,6 +30,8 @@ import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
import androidx.media3.exoplayer.upstream.CmcdConfiguration; import androidx.media3.exoplayer.upstream.CmcdConfiguration;
import androidx.media3.exoplayer.upstream.LoaderErrorThrower; import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
import androidx.media3.extractor.Extractor; import androidx.media3.extractor.Extractor;
import androidx.media3.extractor.text.SubtitleParser;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.List; import java.util.List;
/** A {@link ChunkSource} for DASH streams. */ /** A {@link ChunkSource} for DASH streams. */
@ -39,6 +41,36 @@ public interface DashChunkSource extends ChunkSource {
/** Factory for {@link DashChunkSource}s. */ /** Factory for {@link DashChunkSource}s. */
interface Factory { interface Factory {
/**
* Sets the {@link SubtitleParser.Factory} to use for parsing subtitles during extraction. The
* default factory value is implementation dependent.
*
* @param subtitleParserFactory The {@link SubtitleParser.Factory} for parsing subtitles during
* extraction.
* @return This factory, for convenience.
*/
@CanIgnoreReturnValue
default Factory setSubtitleParserFactory(SubtitleParser.Factory subtitleParserFactory) {
return this;
}
/**
* Sets whether subtitles should be parsed as part of extraction (before being added to the
* sample queue) or as part of rendering (when being taken from the sample queue). Defaults to
* {@code false} (i.e. subtitles will be parsed as part of rendering).
*
* <p>This method is experimental and will be renamed or removed in a future release.
*
* @param parseSubtitlesDuringExtraction Whether to parse subtitles during extraction or
* rendering.
* @return This factory, for convenience.
*/
@CanIgnoreReturnValue
default Factory experimentalParseSubtitlesDuringExtraction(
boolean parseSubtitlesDuringExtraction) {
return this;
}
/** /**
* @param manifestLoaderErrorThrower Throws errors affecting loading of manifests. * @param manifestLoaderErrorThrower Throws errors affecting loading of manifests.
* @param manifest The initial manifest. * @param manifest The initial manifest.

View File

@ -77,8 +77,6 @@ import androidx.media3.exoplayer.upstream.Loader.LoadErrorAction;
import androidx.media3.exoplayer.upstream.LoaderErrorThrower; import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
import androidx.media3.exoplayer.upstream.ParsingLoadable; import androidx.media3.exoplayer.upstream.ParsingLoadable;
import androidx.media3.exoplayer.util.SntpClient; import androidx.media3.exoplayer.util.SntpClient;
import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
import androidx.media3.extractor.text.SubtitleParser;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.math.LongMath; import com.google.common.math.LongMath;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
@ -114,7 +112,6 @@ public final class DashMediaSource extends BaseMediaSource {
private DrmSessionManagerProvider drmSessionManagerProvider; private DrmSessionManagerProvider drmSessionManagerProvider;
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy; private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
@Nullable private SubtitleParser.Factory subtitleParserFactory;
private long fallbackTargetLiveOffsetMs; private long fallbackTargetLiveOffsetMs;
private long minLiveStartPositionUs; private long minLiveStartPositionUs;
@Nullable private ParsingLoadable.Parser<? extends DashManifest> manifestParser; @Nullable private ParsingLoadable.Parser<? extends DashManifest> manifestParser;
@ -199,33 +196,11 @@ public final class DashMediaSource extends BaseMediaSource {
return this; return this;
} }
/**
* {@inheritDoc}
*
* <p>This method may only be used with {@link DefaultDashChunkSource.Factory}.
*
* @param parseSubtitlesDuringExtraction Whether to parse subtitles during extraction or
* rendering.
* @return This factory, for convenience.
*/
// TODO: b/289916598 - Flip the default of this to true.
@Override @Override
@CanIgnoreReturnValue @CanIgnoreReturnValue
public Factory experimentalParseSubtitlesDuringExtraction( public Factory experimentalParseSubtitlesDuringExtraction(
boolean parseSubtitlesDuringExtraction) { boolean parseSubtitlesDuringExtraction) {
if (parseSubtitlesDuringExtraction) { chunkSourceFactory.experimentalParseSubtitlesDuringExtraction(parseSubtitlesDuringExtraction);
if (subtitleParserFactory == null) {
this.subtitleParserFactory = new DefaultSubtitleParserFactory();
}
} else {
this.subtitleParserFactory = null;
}
if (chunkSourceFactory instanceof DefaultDashChunkSource.Factory) {
((DefaultDashChunkSource.Factory) chunkSourceFactory)
.setSubtitleParserFactory(subtitleParserFactory);
} else {
throw new IllegalStateException();
}
return this; return this;
} }

View File

@ -63,6 +63,7 @@ import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
import androidx.media3.extractor.ChunkIndex; import androidx.media3.extractor.ChunkIndex;
import androidx.media3.extractor.text.SubtitleParser; import androidx.media3.extractor.text.SubtitleParser;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -113,18 +114,19 @@ public class DefaultDashChunkSource implements DashChunkSource {
this.maxSegmentsPerLoad = maxSegmentsPerLoad; this.maxSegmentsPerLoad = maxSegmentsPerLoad;
} }
/** @CanIgnoreReturnValue
* Sets the {@link SubtitleParser.Factory} to be used for parsing subtitles during extraction, @Override
* or null to parse subtitles during decoding. public Factory setSubtitleParserFactory(SubtitleParser.Factory subtitleParserFactory) {
* chunkExtractorFactory.setSubtitleParserFactory(subtitleParserFactory);
* <p>This may only be used with {@link BundledChunkExtractor.Factory}. return this;
*/ }
/* package */ Factory setSubtitleParserFactory(
@Nullable SubtitleParser.Factory subtitleParserFactory) { @CanIgnoreReturnValue
if (chunkExtractorFactory instanceof BundledChunkExtractor.Factory) { @Override
((BundledChunkExtractor.Factory) chunkExtractorFactory) public Factory experimentalParseSubtitlesDuringExtraction(
.experimentalSetSubtitleParserFactory(subtitleParserFactory); boolean parseSubtitlesDuringExtraction) {
} chunkExtractorFactory.experimentalParseSubtitlesDuringExtraction(
parseSubtitlesDuringExtraction);
return this; return this;
} }

View File

@ -17,7 +17,6 @@ package androidx.media3.exoplayer.hls;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.util.TimestampAdjuster; import androidx.media3.common.util.TimestampAdjuster;
@ -47,7 +46,8 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract
@VisibleForTesting /* package */ final Extractor extractor; @VisibleForTesting /* package */ final Extractor extractor;
private final Format multivariantPlaylistFormat; private final Format multivariantPlaylistFormat;
private final TimestampAdjuster timestampAdjuster; private final TimestampAdjuster timestampAdjuster;
@Nullable private final SubtitleParser.Factory subtitleParserFactory; private final SubtitleParser.Factory subtitleParserFactory;
private final boolean parseSubtitlesDuringExtraction;
/** /**
* Creates a new instance. * Creates a new instance.
@ -62,7 +62,8 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract
extractor, extractor,
multivariantPlaylistFormat, multivariantPlaylistFormat,
timestampAdjuster, timestampAdjuster,
/* subtitleParserFactory= */ null); SubtitleParser.Factory.UNSUPPORTED,
/* parseSubtitlesDuringExtraction= */ false);
} }
/** /**
@ -77,16 +78,18 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract
* multivariantPlaylistFormat. * multivariantPlaylistFormat.
*/ */
// TODO(b/289983417): Once the subtitle-parsing-during-extraction is the only available flow, make // TODO(b/289983417): Once the subtitle-parsing-during-extraction is the only available flow, make
// this constructor public and remove @Nullable from subtitleParserFactory // this constructor public and remove parseSubtitlesDuringExtraction parameter
/* package */ BundledHlsMediaChunkExtractor( /* package */ BundledHlsMediaChunkExtractor(
Extractor extractor, Extractor extractor,
Format multivariantPlaylistFormat, Format multivariantPlaylistFormat,
TimestampAdjuster timestampAdjuster, TimestampAdjuster timestampAdjuster,
@Nullable SubtitleParser.Factory subtitleParserFactory) { SubtitleParser.Factory subtitleParserFactory,
boolean parseSubtitlesDuringExtraction) {
this.extractor = extractor; this.extractor = extractor;
this.multivariantPlaylistFormat = multivariantPlaylistFormat; this.multivariantPlaylistFormat = multivariantPlaylistFormat;
this.timestampAdjuster = timestampAdjuster; this.timestampAdjuster = timestampAdjuster;
this.subtitleParserFactory = subtitleParserFactory; this.subtitleParserFactory = subtitleParserFactory;
this.parseSubtitlesDuringExtraction = parseSubtitlesDuringExtraction;
} }
@Override @Override
@ -124,18 +127,11 @@ 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) {
SubtitleParser.Factory webvttSubtitleParserFactory = SubtitleParser.Factory.UNSUPPORTED;
boolean parseSubtitlesDuringExtraction = false;
if (subtitleParserFactory != null
&& subtitleParserFactory.supportsFormat(multivariantPlaylistFormat)) {
webvttSubtitleParserFactory = subtitleParserFactory;
parseSubtitlesDuringExtraction = true;
}
newExtractorInstance = newExtractorInstance =
new WebvttExtractor( new WebvttExtractor(
multivariantPlaylistFormat.language, multivariantPlaylistFormat.language,
timestampAdjuster, timestampAdjuster,
webvttSubtitleParserFactory, subtitleParserFactory,
parseSubtitlesDuringExtraction); parseSubtitlesDuringExtraction);
} else if (extractor instanceof AdtsExtractor) { } else if (extractor instanceof AdtsExtractor) {
newExtractorInstance = new AdtsExtractor(); newExtractorInstance = new AdtsExtractor();
@ -150,7 +146,11 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract
"Unexpected extractor type for recreation: " + extractor.getClass().getSimpleName()); "Unexpected extractor type for recreation: " + extractor.getClass().getSimpleName());
} }
return new BundledHlsMediaChunkExtractor( return new BundledHlsMediaChunkExtractor(
newExtractorInstance, multivariantPlaylistFormat, timestampAdjuster, subtitleParserFactory); newExtractorInstance,
multivariantPlaylistFormat,
timestampAdjuster,
subtitleParserFactory,
parseSubtitlesDuringExtraction);
} }
@Override @Override

View File

@ -33,6 +33,7 @@ import androidx.media3.extractor.Extractor;
import androidx.media3.extractor.ExtractorInput; 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.DefaultSubtitleParserFactory;
import androidx.media3.extractor.text.SubtitleParser; import androidx.media3.extractor.text.SubtitleParser;
import androidx.media3.extractor.ts.Ac3Extractor; import androidx.media3.extractor.ts.Ac3Extractor;
import androidx.media3.extractor.ts.Ac4Extractor; import androidx.media3.extractor.ts.Ac4Extractor;
@ -41,6 +42,7 @@ import androidx.media3.extractor.ts.DefaultTsPayloadReaderFactory;
import androidx.media3.extractor.ts.TsExtractor; import androidx.media3.extractor.ts.TsExtractor;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -67,8 +69,8 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
private final @DefaultTsPayloadReaderFactory.Flags int payloadReaderFactoryFlags; private final @DefaultTsPayloadReaderFactory.Flags int payloadReaderFactoryFlags;
/** Non-null if subtitles should be parsed during extraction, null otherwise. */ private SubtitleParser.Factory subtitleParserFactory;
@Nullable private SubtitleParser.Factory subtitleParserFactory; private boolean parseSubtitlesDuringExtraction;
private final boolean exposeCea608WhenMissingDeclarations; private final boolean exposeCea608WhenMissingDeclarations;
@ -96,6 +98,7 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
int payloadReaderFactoryFlags, boolean exposeCea608WhenMissingDeclarations) { int payloadReaderFactoryFlags, boolean exposeCea608WhenMissingDeclarations) {
this.payloadReaderFactoryFlags = payloadReaderFactoryFlags; this.payloadReaderFactoryFlags = payloadReaderFactoryFlags;
this.exposeCea608WhenMissingDeclarations = exposeCea608WhenMissingDeclarations; this.exposeCea608WhenMissingDeclarations = exposeCea608WhenMissingDeclarations;
subtitleParserFactory = new DefaultSubtitleParserFactory();
} }
@Override @Override
@ -135,7 +138,11 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
createExtractorByFileType(fileType, format, muxedCaptionFormats, timestampAdjuster)); createExtractorByFileType(fileType, format, muxedCaptionFormats, timestampAdjuster));
if (sniffQuietly(extractor, sniffingExtractorInput)) { if (sniffQuietly(extractor, sniffingExtractorInput)) {
return new BundledHlsMediaChunkExtractor( return new BundledHlsMediaChunkExtractor(
extractor, format, timestampAdjuster, subtitleParserFactory); extractor,
format,
timestampAdjuster,
subtitleParserFactory,
parseSubtitlesDuringExtraction);
} }
if (fallBackExtractor == null if (fallBackExtractor == null
&& (fileType == formatInferredFileType && (fileType == formatInferredFileType
@ -149,26 +156,29 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
} }
return new BundledHlsMediaChunkExtractor( return new BundledHlsMediaChunkExtractor(
checkNotNull(fallBackExtractor), format, timestampAdjuster, subtitleParserFactory); checkNotNull(fallBackExtractor),
format,
timestampAdjuster,
subtitleParserFactory,
parseSubtitlesDuringExtraction);
} }
/** @CanIgnoreReturnValue
* Sets the {@link SubtitleParser.Factory} to use for parsing subtitles during extraction, or null @Override
* to parse subtitles during decoding. The default is null (subtitles parsed after decoding). public DefaultHlsExtractorFactory setSubtitleParserFactory(
* SubtitleParser.Factory subtitleParserFactory) {
* <p>This method is experimental. Its default value may change, or it may be renamed or removed
* in a future release.
*
* @param subtitleParserFactory The {@link SubtitleParser.Factory} for parsing subtitles during
* extraction.
* @return This factory, for convenience.
*/
public DefaultHlsExtractorFactory experimentalSetSubtitleParserFactory(
@Nullable SubtitleParser.Factory subtitleParserFactory) {
this.subtitleParserFactory = subtitleParserFactory; this.subtitleParserFactory = subtitleParserFactory;
return this; return this;
} }
@CanIgnoreReturnValue
@Override
public DefaultHlsExtractorFactory experimentalParseSubtitlesDuringExtraction(
boolean parseSubtitlesDuringExtraction) {
this.parseSubtitlesDuringExtraction = parseSubtitlesDuringExtraction;
return this;
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
@ -176,18 +186,15 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
* MimeTypes#APPLICATION_MEDIA3_CUES} if it is supported by {@link SubtitleParser.Factory}. * MimeTypes#APPLICATION_MEDIA3_CUES} if it is supported by {@link SubtitleParser.Factory}.
* *
* <p>To modify the support behavior, you can {@linkplain * <p>To modify the support behavior, you can {@linkplain
* #experimentalSetSubtitleParserFactory(SubtitleParser.Factory) set your own subtitle parser * #setSubtitleParserFactory(SubtitleParser.Factory) set your own subtitle parser factory}.
* factory}.
*/ */
@Override @Override
public Format getOutputTextFormat(Format sourceFormat) { public Format getOutputTextFormat(Format sourceFormat) {
if (subtitleParserFactory != null && subtitleParserFactory.supportsFormat(sourceFormat)) { if (parseSubtitlesDuringExtraction && subtitleParserFactory.supportsFormat(sourceFormat)) {
@Format.CueReplacementBehavior
int cueReplacementBehavior = subtitleParserFactory.getCueReplacementBehavior(sourceFormat);
return sourceFormat return sourceFormat
.buildUpon() .buildUpon()
.setSampleMimeType(MimeTypes.APPLICATION_MEDIA3_CUES) .setSampleMimeType(MimeTypes.APPLICATION_MEDIA3_CUES)
.setCueReplacementBehavior(cueReplacementBehavior) .setCueReplacementBehavior(subtitleParserFactory.getCueReplacementBehavior(sourceFormat))
.setCodecs( .setCodecs(
sourceFormat.sampleMimeType sourceFormat.sampleMimeType
+ (sourceFormat.codecs != null ? " " + sourceFormat.codecs : "")) + (sourceFormat.codecs != null ? " " + sourceFormat.codecs : ""))
@ -216,16 +223,10 @@ 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)) {
webvttSubtitleParserFactory = subtitleParserFactory;
parseSubtitlesDuringExtraction = true;
}
return new WebvttExtractor( return new WebvttExtractor(
format.language, format.language,
timestampAdjuster, timestampAdjuster,
webvttSubtitleParserFactory, subtitleParserFactory,
parseSubtitlesDuringExtraction); parseSubtitlesDuringExtraction);
case FileTypes.ADTS: case FileTypes.ADTS:
return new AdtsExtractor(); return new AdtsExtractor();
@ -237,7 +238,11 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
return new Mp3Extractor(/* flags= */ 0, /* forcedFirstSampleTimestampUs= */ 0); return new Mp3Extractor(/* flags= */ 0, /* forcedFirstSampleTimestampUs= */ 0);
case FileTypes.MP4: case FileTypes.MP4:
return createFragmentedMp4Extractor( return createFragmentedMp4Extractor(
subtitleParserFactory, timestampAdjuster, format, muxedCaptionFormats); subtitleParserFactory,
parseSubtitlesDuringExtraction,
timestampAdjuster,
format,
muxedCaptionFormats);
case FileTypes.TS: case FileTypes.TS:
return createTsExtractor( return createTsExtractor(
payloadReaderFactoryFlags, payloadReaderFactoryFlags,
@ -245,7 +250,8 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
format, format,
muxedCaptionFormats, muxedCaptionFormats,
timestampAdjuster, timestampAdjuster,
subtitleParserFactory); subtitleParserFactory,
parseSubtitlesDuringExtraction);
default: default:
return null; return null;
} }
@ -257,7 +263,8 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
Format format, Format format,
@Nullable List<Format> muxedCaptionFormats, @Nullable List<Format> muxedCaptionFormats,
TimestampAdjuster timestampAdjuster, TimestampAdjuster timestampAdjuster,
@Nullable SubtitleParser.Factory subtitleParserFactory) { SubtitleParser.Factory subtitleParserFactory,
boolean parseSubtitlesDuringExtraction) {
@DefaultTsPayloadReaderFactory.Flags @DefaultTsPayloadReaderFactory.Flags
int payloadReaderFactoryFlags = int payloadReaderFactoryFlags =
DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM
@ -287,7 +294,7 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
} }
} }
@TsExtractor.Flags int extractorFlags = 0; @TsExtractor.Flags int extractorFlags = 0;
if (subtitleParserFactory == null) { if (!parseSubtitlesDuringExtraction) {
subtitleParserFactory = SubtitleParser.Factory.UNSUPPORTED; subtitleParserFactory = SubtitleParser.Factory.UNSUPPORTED;
extractorFlags |= TsExtractor.FLAG_EMIT_RAW_SUBTITLE_DATA; extractorFlags |= TsExtractor.FLAG_EMIT_RAW_SUBTITLE_DATA;
} }
@ -301,7 +308,8 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
} }
private static FragmentedMp4Extractor createFragmentedMp4Extractor( private static FragmentedMp4Extractor createFragmentedMp4Extractor(
@Nullable SubtitleParser.Factory subtitleParserFactory, SubtitleParser.Factory subtitleParserFactory,
boolean parseSubtitlesDuringExtraction,
TimestampAdjuster timestampAdjuster, TimestampAdjuster timestampAdjuster,
Format format, Format format,
@Nullable List<Format> muxedCaptionFormats) { @Nullable List<Format> muxedCaptionFormats) {
@ -309,7 +317,7 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
// creating a separate EMSG track for every audio track in a video stream. // creating a separate EMSG track for every audio track in a video stream.
@FragmentedMp4Extractor.Flags @FragmentedMp4Extractor.Flags
int flags = isFmp4Variant(format) ? FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK : 0; int flags = isFmp4Variant(format) ? FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK : 0;
if (subtitleParserFactory == null) { if (!parseSubtitlesDuringExtraction) {
subtitleParserFactory = SubtitleParser.Factory.UNSUPPORTED; subtitleParserFactory = SubtitleParser.Factory.UNSUPPORTED;
flags |= FragmentedMp4Extractor.FLAG_EMIT_RAW_SUBTITLE_DATA; flags |= FragmentedMp4Extractor.FLAG_EMIT_RAW_SUBTITLE_DATA;
} }

View File

@ -26,6 +26,8 @@ import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.extractor.Extractor; import androidx.media3.extractor.Extractor;
import androidx.media3.extractor.ExtractorInput; import androidx.media3.extractor.ExtractorInput;
import androidx.media3.extractor.PositionHolder; import androidx.media3.extractor.PositionHolder;
import androidx.media3.extractor.text.SubtitleParser;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -63,6 +65,37 @@ public interface HlsExtractorFactory {
PlayerId playerId) PlayerId playerId)
throws IOException; throws IOException;
/**
* Sets the {@link SubtitleParser.Factory} to use for parsing subtitles during extraction. The
* default factory value is implementation dependent.
*
* @param subtitleParserFactory The {@link SubtitleParser.Factory} for parsing subtitles during
* extraction.
* @return This factory, for convenience.
*/
@CanIgnoreReturnValue
default HlsExtractorFactory setSubtitleParserFactory(
SubtitleParser.Factory subtitleParserFactory) {
return this;
}
/**
* Sets whether subtitles should be parsed as part of extraction (before being added to the sample
* queue) or as part of rendering (when being taken from the sample queue). Defaults to {@code
* false} (i.e. subtitles will be parsed as part of rendering).
*
* <p>This method is experimental and will be renamed or removed in a future release.
*
* @param parseSubtitlesDuringExtraction Whether to parse subtitles during extraction or
* rendering.
* @return This factory, for convenience.
*/
@CanIgnoreReturnValue
default HlsExtractorFactory experimentalParseSubtitlesDuringExtraction(
boolean parseSubtitlesDuringExtraction) {
return this;
}
/** /**
* Returns the output {@link Format} of emitted {@linkplain C#TRACK_TYPE_TEXT text samples} which * Returns the output {@link Format} of emitted {@linkplain C#TRACK_TYPE_TEXT text samples} which
* were originally in {@code sourceFormat}. * were originally in {@code sourceFormat}.

View File

@ -58,8 +58,6 @@ import androidx.media3.exoplayer.upstream.CmcdConfiguration;
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy; import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy;
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy; import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
import androidx.media3.extractor.Extractor; import androidx.media3.extractor.Extractor;
import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
import androidx.media3.extractor.text.SubtitleParser;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
@ -113,7 +111,7 @@ public final class HlsMediaSource extends BaseMediaSource
@Nullable private CmcdConfiguration.Factory cmcdConfigurationFactory; @Nullable private CmcdConfiguration.Factory cmcdConfigurationFactory;
private DrmSessionManagerProvider drmSessionManagerProvider; private DrmSessionManagerProvider drmSessionManagerProvider;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy; private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
@Nullable private SubtitleParser.Factory subtitleParserFactory;
private boolean allowChunklessPreparation; private boolean allowChunklessPreparation;
private @MetadataType int metadataType; private @MetadataType int metadataType;
private boolean useSessionKeys; private boolean useSessionKeys;
@ -199,32 +197,11 @@ public final class HlsMediaSource extends BaseMediaSource
return this; return this;
} }
/**
* {@inheritDoc}
*
* <p>This method may only be used with {@link DefaultHlsExtractorFactory}.
*
* @param parseSubtitlesDuringExtraction Whether to parse subtitles during extraction or
* rendering.
* @return This factory, for convenience.
*/
// TODO: b/289916598 - Flip the default of this to true.
@Override @Override
@CanIgnoreReturnValue
public Factory experimentalParseSubtitlesDuringExtraction( public Factory experimentalParseSubtitlesDuringExtraction(
boolean parseSubtitlesDuringExtraction) { boolean parseSubtitlesDuringExtraction) {
if (parseSubtitlesDuringExtraction) { extractorFactory.experimentalParseSubtitlesDuringExtraction(parseSubtitlesDuringExtraction);
if (subtitleParserFactory == null) {
this.subtitleParserFactory = new DefaultSubtitleParserFactory();
}
} else {
this.subtitleParserFactory = null;
}
if (extractorFactory instanceof DefaultHlsExtractorFactory) {
((DefaultHlsExtractorFactory) extractorFactory)
.experimentalSetSubtitleParserFactory(subtitleParserFactory);
} else {
throw new IllegalStateException();
}
return this; return this;
} }

View File

@ -53,8 +53,10 @@ import androidx.media3.extractor.Extractor;
import androidx.media3.extractor.mp4.FragmentedMp4Extractor; import androidx.media3.extractor.mp4.FragmentedMp4Extractor;
import androidx.media3.extractor.mp4.Track; import androidx.media3.extractor.mp4.Track;
import androidx.media3.extractor.mp4.TrackEncryptionBox; import androidx.media3.extractor.mp4.TrackEncryptionBox;
import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
import androidx.media3.extractor.text.SubtitleParser; import androidx.media3.extractor.text.SubtitleParser;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -65,24 +67,29 @@ public class DefaultSsChunkSource implements SsChunkSource {
public static final class Factory implements SsChunkSource.Factory { public static final class Factory implements SsChunkSource.Factory {
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
@Nullable private SubtitleParser.Factory subtitleParserFactory; private SubtitleParser.Factory subtitleParserFactory;
private boolean parseSubtitlesDuringExtraction;
public Factory(DataSource.Factory dataSourceFactory) { public Factory(DataSource.Factory dataSourceFactory) {
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
subtitleParserFactory = new DefaultSubtitleParserFactory();
} }
/** @CanIgnoreReturnValue
* Sets the {@link SubtitleParser.Factory} to be used for parsing subtitles during extraction, @Override
* or {@code null} to parse subtitles during decoding. public Factory setSubtitleParserFactory(SubtitleParser.Factory subtitleParserFactory) {
*
* <p>This may only be used with {@link BundledChunkExtractor.Factory}.
*/
/* package */ Factory setSubtitleParserFactory(
@Nullable SubtitleParser.Factory subtitleParserFactory) {
this.subtitleParserFactory = subtitleParserFactory; this.subtitleParserFactory = subtitleParserFactory;
return this; return this;
} }
@CanIgnoreReturnValue
@Override
public Factory experimentalParseSubtitlesDuringExtraction(
boolean parseSubtitlesDuringExtraction) {
this.parseSubtitlesDuringExtraction = parseSubtitlesDuringExtraction;
return this;
}
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
@ -91,13 +98,12 @@ public class DefaultSsChunkSource implements SsChunkSource {
*/ */
@Override @Override
public Format getOutputTextFormat(Format sourceFormat) { public Format getOutputTextFormat(Format sourceFormat) {
if (subtitleParserFactory != null && subtitleParserFactory.supportsFormat(sourceFormat)) { if (parseSubtitlesDuringExtraction && subtitleParserFactory.supportsFormat(sourceFormat)) {
@Format.CueReplacementBehavior
int cueReplacementBehavior = subtitleParserFactory.getCueReplacementBehavior(sourceFormat);
return sourceFormat return sourceFormat
.buildUpon() .buildUpon()
.setSampleMimeType(MimeTypes.APPLICATION_MEDIA3_CUES) .setSampleMimeType(MimeTypes.APPLICATION_MEDIA3_CUES)
.setCueReplacementBehavior(cueReplacementBehavior) .setCueReplacementBehavior(
subtitleParserFactory.getCueReplacementBehavior(sourceFormat))
.setCodecs( .setCodecs(
sourceFormat.sampleMimeType sourceFormat.sampleMimeType
+ (sourceFormat.codecs != null ? " " + sourceFormat.codecs : "")) + (sourceFormat.codecs != null ? " " + sourceFormat.codecs : ""))
@ -127,7 +133,8 @@ public class DefaultSsChunkSource implements SsChunkSource {
trackSelection, trackSelection,
dataSource, dataSource,
cmcdConfiguration, cmcdConfiguration,
subtitleParserFactory); subtitleParserFactory,
parseSubtitlesDuringExtraction);
} }
} }
@ -158,6 +165,8 @@ public class DefaultSsChunkSource implements SsChunkSource {
* @param cmcdConfiguration The {@link CmcdConfiguration} for this chunk source. * @param cmcdConfiguration The {@link CmcdConfiguration} for this chunk source.
* @param subtitleParserFactory The {@link SubtitleParser.Factory} for parsing subtitles during * @param subtitleParserFactory The {@link SubtitleParser.Factory} for parsing subtitles during
* extraction. * extraction.
* @param parseSubtitlesDuringExtraction Whether to parse subtitles during extraction or
* rendering.
*/ */
public DefaultSsChunkSource( public DefaultSsChunkSource(
LoaderErrorThrower manifestLoaderErrorThrower, LoaderErrorThrower manifestLoaderErrorThrower,
@ -166,7 +175,8 @@ public class DefaultSsChunkSource implements SsChunkSource {
ExoTrackSelection trackSelection, ExoTrackSelection trackSelection,
DataSource dataSource, DataSource dataSource,
@Nullable CmcdConfiguration cmcdConfiguration, @Nullable CmcdConfiguration cmcdConfiguration,
@Nullable SubtitleParser.Factory subtitleParserFactory) { SubtitleParser.Factory subtitleParserFactory,
boolean parseSubtitlesDuringExtraction) {
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.manifest = manifest; this.manifest = manifest;
this.streamElementIndex = streamElementIndex; this.streamElementIndex = streamElementIndex;
@ -203,14 +213,13 @@ public class DefaultSsChunkSource implements SsChunkSource {
int flags = int flags =
FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
| FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX; | FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX;
if (!parseSubtitlesDuringExtraction) {
flags |= FragmentedMp4Extractor.FLAG_EMIT_RAW_SUBTITLE_DATA;
}
Extractor extractor = Extractor extractor =
new FragmentedMp4Extractor( new FragmentedMp4Extractor(
subtitleParserFactory == null subtitleParserFactory,
? SubtitleParser.Factory.UNSUPPORTED flags,
: subtitleParserFactory,
subtitleParserFactory == null
? flags | FragmentedMp4Extractor.FLAG_EMIT_RAW_SUBTITLE_DATA
: flags,
/* timestampAdjuster= */ null, /* timestampAdjuster= */ null,
track, track,
/* closedCaptionFormats= */ ImmutableList.of(), /* closedCaptionFormats= */ ImmutableList.of(),

View File

@ -27,6 +27,8 @@ import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
import androidx.media3.exoplayer.upstream.CmcdConfiguration; import androidx.media3.exoplayer.upstream.CmcdConfiguration;
import androidx.media3.exoplayer.upstream.LoaderErrorThrower; import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
import androidx.media3.extractor.Extractor; import androidx.media3.extractor.Extractor;
import androidx.media3.extractor.text.SubtitleParser;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
/** A {@link ChunkSource} for SmoothStreaming. */ /** A {@link ChunkSource} for SmoothStreaming. */
@UnstableApi @UnstableApi
@ -55,6 +57,36 @@ public interface SsChunkSource extends ChunkSource {
@Nullable TransferListener transferListener, @Nullable TransferListener transferListener,
@Nullable CmcdConfiguration cmcdConfiguration); @Nullable CmcdConfiguration cmcdConfiguration);
/**
* Sets the {@link SubtitleParser.Factory} to use for parsing subtitles during extraction. The
* default factory value is implementation dependent.
*
* @param subtitleParserFactory The {@link SubtitleParser.Factory} for parsing subtitles during
* extraction.
* @return This factory, for convenience.
*/
@CanIgnoreReturnValue
default Factory setSubtitleParserFactory(SubtitleParser.Factory subtitleParserFactory) {
return this;
}
/**
* Sets whether subtitles should be parsed as part of extraction (before being added to the
* sample queue) or as part of rendering (when being taken from the sample queue). Defaults to
* {@code false} (i.e. subtitles will be parsed as part of rendering).
*
* <p>This method is experimental and will be renamed or removed in a future release.
*
* @param parseSubtitlesDuringExtraction Whether to parse subtitles during extraction or
* rendering.
* @return This factory, for convenience.
*/
@CanIgnoreReturnValue
default Factory experimentalParseSubtitlesDuringExtraction(
boolean parseSubtitlesDuringExtraction) {
return this;
}
/** /**
* Returns the output {@link Format} of emitted {@linkplain C#TRACK_TYPE_TEXT text samples} * Returns the output {@link Format} of emitted {@linkplain C#TRACK_TYPE_TEXT text samples}
* which were originally in {@code sourceFormat}. * which were originally in {@code sourceFormat}.

View File

@ -65,8 +65,6 @@ import androidx.media3.exoplayer.upstream.Loader;
import androidx.media3.exoplayer.upstream.Loader.LoadErrorAction; import androidx.media3.exoplayer.upstream.Loader.LoadErrorAction;
import androidx.media3.exoplayer.upstream.LoaderErrorThrower; import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
import androidx.media3.exoplayer.upstream.ParsingLoadable; import androidx.media3.exoplayer.upstream.ParsingLoadable;
import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
import androidx.media3.extractor.text.SubtitleParser;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException; import java.io.IOException;
@ -93,7 +91,6 @@ public final class SsMediaSource extends BaseMediaSource
@Nullable private CmcdConfiguration.Factory cmcdConfigurationFactory; @Nullable private CmcdConfiguration.Factory cmcdConfigurationFactory;
private DrmSessionManagerProvider drmSessionManagerProvider; private DrmSessionManagerProvider drmSessionManagerProvider;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy; private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
@Nullable private SubtitleParser.Factory subtitleParserFactory;
private long livePresentationDelayMs; private long livePresentationDelayMs;
@Nullable private ParsingLoadable.Parser<? extends SsManifest> manifestParser; @Nullable private ParsingLoadable.Parser<? extends SsManifest> manifestParser;
@ -155,32 +152,11 @@ public final class SsMediaSource extends BaseMediaSource
return this; return this;
} }
/**
* {@inheritDoc}
*
* <p>This method may only be used with {@link DefaultSsChunkSource.Factory}.
*
* @param parseSubtitlesDuringExtraction Whether to parse subtitles during extraction or
* rendering.
* @return This factory, for convenience.
*/
// TODO: b/289916598 - Flip the default of this to true.
@Override @Override
@CanIgnoreReturnValue
public Factory experimentalParseSubtitlesDuringExtraction( public Factory experimentalParseSubtitlesDuringExtraction(
boolean parseSubtitlesDuringExtraction) { boolean parseSubtitlesDuringExtraction) {
if (parseSubtitlesDuringExtraction) { chunkSourceFactory.experimentalParseSubtitlesDuringExtraction(parseSubtitlesDuringExtraction);
if (subtitleParserFactory == null) {
this.subtitleParserFactory = new DefaultSubtitleParserFactory();
}
} else {
this.subtitleParserFactory = null;
}
if (chunkSourceFactory instanceof DefaultSsChunkSource.Factory) {
((DefaultSsChunkSource.Factory) chunkSourceFactory)
.setSubtitleParserFactory(subtitleParserFactory);
} else {
throw new IllegalStateException();
}
return this; return this;
} }

View File

@ -33,6 +33,7 @@ import androidx.media3.exoplayer.trackselection.AdaptiveTrackSelection;
import androidx.media3.exoplayer.upstream.CmcdConfiguration; import androidx.media3.exoplayer.upstream.CmcdConfiguration;
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter; import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter;
import androidx.media3.exoplayer.upstream.LoaderErrorThrower; import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
import androidx.media3.extractor.text.SubtitleParser;
import androidx.media3.test.utils.FakeDataSource; import androidx.media3.test.utils.FakeDataSource;
import androidx.media3.test.utils.TestUtil; import androidx.media3.test.utils.TestUtil;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
@ -315,6 +316,7 @@ public class DefaultSsChunkSourceTest {
adaptiveTrackSelection, adaptiveTrackSelection,
new FakeDataSource(), new FakeDataSource(),
cmcdConfiguration, cmcdConfiguration,
/* subtitleParserFactory= */ null); SubtitleParser.Factory.UNSUPPORTED,
/* parseSubtitlesDuringExtraction= */ false);
} }
} }

View File

@ -16,7 +16,6 @@
package androidx.media3.exoplayer.smoothstreaming; package androidx.media3.exoplayer.smoothstreaming;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -33,6 +32,7 @@ import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.trackselection.ExoTrackSelection; import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
import androidx.media3.exoplayer.upstream.CmcdConfiguration; import androidx.media3.exoplayer.upstream.CmcdConfiguration;
import androidx.media3.exoplayer.upstream.LoaderErrorThrower; import androidx.media3.exoplayer.upstream.LoaderErrorThrower;
import androidx.media3.extractor.text.SubtitleParser;
import androidx.media3.test.utils.FakeDataSource; import androidx.media3.test.utils.FakeDataSource;
import androidx.media3.test.utils.TestUtil; import androidx.media3.test.utils.TestUtil;
import androidx.media3.test.utils.robolectric.RobolectricUtil; import androidx.media3.test.utils.robolectric.RobolectricUtil;
@ -104,14 +104,12 @@ public class SsMediaSourceTest {
@Test @Test
public void public void
setExperimentalParseSubtitlesDuringExtraction_withNonDefaultChunkSourceFactory_setThrows() { setExperimentalParseSubtitlesDuringExtraction_withNonDefaultChunkSourceFactory_setSucceeds() {
SsMediaSource.Factory ssMediaSourceFactory = SsMediaSource.Factory ssMediaSourceFactory =
new SsMediaSource.Factory( new SsMediaSource.Factory(
/* chunkSourceFactory= */ this::createSampleSsChunkSource, /* chunkSourceFactory= */ this::createSampleSsChunkSource,
/* manifestDataSourceFactory= */ () -> createSampleDataSource(SAMPLE_MANIFEST)); /* manifestDataSourceFactory= */ () -> createSampleDataSource(SAMPLE_MANIFEST));
assertThrows( ssMediaSourceFactory.experimentalParseSubtitlesDuringExtraction(false);
IllegalStateException.class,
() -> ssMediaSourceFactory.experimentalParseSubtitlesDuringExtraction(false));
} }
@Test @Test
@ -219,6 +217,7 @@ public class SsMediaSourceTest {
trackSelection, trackSelection,
new FakeDataSource(), new FakeDataSource(),
cmcdConfiguration, cmcdConfiguration,
/* subtitleParserFactory= */ null); SubtitleParser.Factory.UNSUPPORTED,
/* parseSubtitlesDuringExtraction= */ false);
} }
} }