mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Add experimental opt-in to parse HLS subtitles during extraction
PiperOrigin-RevId: 578524318
This commit is contained in:
parent
72b7019578
commit
7b762642db
7 changed files with 152 additions and 14 deletions
|
|
@ -46,6 +46,9 @@
|
||||||
* HLS Extension:
|
* HLS Extension:
|
||||||
* Reduce `HlsMediaPeriod` to package-private visibility. This type
|
* Reduce `HlsMediaPeriod` to package-private visibility. This type
|
||||||
shouldn't be directly depended on from outside the HLS package.
|
shouldn't be directly depended on from outside the HLS package.
|
||||||
|
* Add experimental support for parsing subtitles during extraction. You
|
||||||
|
can enable this using
|
||||||
|
`HlsMediaSource.Factory.experimentalParseSubtitlesDuringExtraction()`.
|
||||||
* DASH Extension:
|
* DASH Extension:
|
||||||
* Extend experimental support for parsing subtitles during extraction to
|
* Extend experimental support for parsing subtitles during extraction to
|
||||||
work with standalone text files (previously it only worked with
|
work with standalone text files (previously it only worked with
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ 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;
|
||||||
|
|
@ -27,6 +28,8 @@ import androidx.media3.extractor.ExtractorOutput;
|
||||||
import androidx.media3.extractor.PositionHolder;
|
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.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;
|
||||||
|
|
@ -45,6 +48,7 @@ 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* Creates a new instance.
|
||||||
|
|
@ -55,9 +59,35 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract
|
||||||
*/
|
*/
|
||||||
public BundledHlsMediaChunkExtractor(
|
public BundledHlsMediaChunkExtractor(
|
||||||
Extractor extractor, Format multivariantPlaylistFormat, TimestampAdjuster timestampAdjuster) {
|
Extractor extractor, Format multivariantPlaylistFormat, TimestampAdjuster timestampAdjuster) {
|
||||||
|
this(
|
||||||
|
extractor,
|
||||||
|
multivariantPlaylistFormat,
|
||||||
|
timestampAdjuster,
|
||||||
|
/* subtitleParserFactory= */ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* @param extractor The underlying {@link Extractor}.
|
||||||
|
* @param multivariantPlaylistFormat The {@link Format} obtained from the multivariant playlist.
|
||||||
|
* @param timestampAdjuster A {@link TimestampAdjuster} to adjust sample timestamps.
|
||||||
|
* @param subtitleParserFactory A {@link SubtitleParser.Factory} to be used with WebVTT subtitles.
|
||||||
|
* If the value is null, subtitles will be parsed during decoding, otherwise - during
|
||||||
|
* extraction. Decoding will only work if this subtitleParserFactory supports the provided
|
||||||
|
* multivariantPlaylistFormat.
|
||||||
|
*/
|
||||||
|
// TODO(b/289983417): Once the subtitle-parsing-during-extraction is the only available flow, make
|
||||||
|
// this constructor public and remove @Nullable from subtitleParserFactory
|
||||||
|
/* package */ BundledHlsMediaChunkExtractor(
|
||||||
|
Extractor extractor,
|
||||||
|
Format multivariantPlaylistFormat,
|
||||||
|
TimestampAdjuster timestampAdjuster,
|
||||||
|
@Nullable SubtitleParser.Factory subtitleParserFactory) {
|
||||||
this.extractor = extractor;
|
this.extractor = extractor;
|
||||||
this.multivariantPlaylistFormat = multivariantPlaylistFormat;
|
this.multivariantPlaylistFormat = multivariantPlaylistFormat;
|
||||||
this.timestampAdjuster = timestampAdjuster;
|
this.timestampAdjuster = timestampAdjuster;
|
||||||
|
this.subtitleParserFactory = subtitleParserFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -97,6 +127,11 @@ public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtract
|
||||||
if (extractor instanceof WebvttExtractor) {
|
if (extractor instanceof WebvttExtractor) {
|
||||||
newExtractorInstance =
|
newExtractorInstance =
|
||||||
new WebvttExtractor(multivariantPlaylistFormat.language, timestampAdjuster);
|
new WebvttExtractor(multivariantPlaylistFormat.language, timestampAdjuster);
|
||||||
|
if (subtitleParserFactory != null
|
||||||
|
&& subtitleParserFactory.supportsFormat(multivariantPlaylistFormat)) {
|
||||||
|
newExtractorInstance =
|
||||||
|
new SubtitleTranscodingExtractor(newExtractorInstance, subtitleParserFactory);
|
||||||
|
}
|
||||||
} 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) {
|
||||||
|
|
@ -110,7 +145,7 @@ 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);
|
newExtractorInstance, multivariantPlaylistFormat, timestampAdjuster, subtitleParserFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ 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.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;
|
||||||
|
|
@ -63,6 +65,10 @@ 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. */
|
||||||
|
@Nullable private SubtitleParser.Factory subtitleParserFactory;
|
||||||
|
|
||||||
private final boolean exposeCea608WhenMissingDeclarations;
|
private final boolean exposeCea608WhenMissingDeclarations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -127,7 +133,8 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
||||||
checkNotNull(
|
checkNotNull(
|
||||||
createExtractorByFileType(fileType, format, muxedCaptionFormats, timestampAdjuster));
|
createExtractorByFileType(fileType, format, muxedCaptionFormats, timestampAdjuster));
|
||||||
if (sniffQuietly(extractor, sniffingExtractorInput)) {
|
if (sniffQuietly(extractor, sniffingExtractorInput)) {
|
||||||
return new BundledHlsMediaChunkExtractor(extractor, format, timestampAdjuster);
|
return new BundledHlsMediaChunkExtractor(
|
||||||
|
extractor, format, timestampAdjuster, subtitleParserFactory);
|
||||||
}
|
}
|
||||||
if (fallBackExtractor == null
|
if (fallBackExtractor == null
|
||||||
&& (fileType == formatInferredFileType
|
&& (fileType == formatInferredFileType
|
||||||
|
|
@ -141,7 +148,24 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BundledHlsMediaChunkExtractor(
|
return new BundledHlsMediaChunkExtractor(
|
||||||
checkNotNull(fallBackExtractor), format, timestampAdjuster);
|
checkNotNull(fallBackExtractor), format, timestampAdjuster, subtitleParserFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link SubtitleParser.Factory} to use for parsing subtitles during extraction, or null
|
||||||
|
* to parse subtitles during decoding. The default is null (subtitles parsed after decoding).
|
||||||
|
*
|
||||||
|
* <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;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addFileTypeIfValidAndNotPresent(
|
private static void addFileTypeIfValidAndNotPresent(
|
||||||
|
|
@ -162,7 +186,12 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
||||||
// LINT.IfChange(extractor_instantiation)
|
// LINT.IfChange(extractor_instantiation)
|
||||||
switch (fileType) {
|
switch (fileType) {
|
||||||
case FileTypes.WEBVTT:
|
case FileTypes.WEBVTT:
|
||||||
return new WebvttExtractor(format.language, timestampAdjuster);
|
if (subtitleParserFactory != null && subtitleParserFactory.supportsFormat(format)) {
|
||||||
|
return new SubtitleTranscodingExtractor(
|
||||||
|
new WebvttExtractor(format.language, timestampAdjuster), subtitleParserFactory);
|
||||||
|
} else {
|
||||||
|
return new WebvttExtractor(format.language, timestampAdjuster);
|
||||||
|
}
|
||||||
case FileTypes.ADTS:
|
case FileTypes.ADTS:
|
||||||
return new AdtsExtractor();
|
return new AdtsExtractor();
|
||||||
case FileTypes.AC3:
|
case FileTypes.AC3:
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ import androidx.media3.exoplayer.upstream.Allocator;
|
||||||
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
|
||||||
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.SubtitleParser;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -85,6 +86,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
private final PlayerId playerId;
|
private final PlayerId playerId;
|
||||||
private final HlsSampleStreamWrapper.Callback sampleStreamWrapperCallback;
|
private final HlsSampleStreamWrapper.Callback sampleStreamWrapperCallback;
|
||||||
private final long timestampAdjusterInitializationTimeoutMs;
|
private final long timestampAdjusterInitializationTimeoutMs;
|
||||||
|
@Nullable private final SubtitleParser.Factory subtitleParserFactory;
|
||||||
|
|
||||||
@Nullable private MediaPeriod.Callback mediaPeriodCallback;
|
@Nullable private MediaPeriod.Callback mediaPeriodCallback;
|
||||||
private int pendingPrepareCount;
|
private int pendingPrepareCount;
|
||||||
|
|
@ -139,7 +141,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
@HlsMediaSource.MetadataType int metadataType,
|
@HlsMediaSource.MetadataType int metadataType,
|
||||||
boolean useSessionKeys,
|
boolean useSessionKeys,
|
||||||
PlayerId playerId,
|
PlayerId playerId,
|
||||||
long timestampAdjusterInitializationTimeoutMs) {
|
long timestampAdjusterInitializationTimeoutMs,
|
||||||
|
@Nullable SubtitleParser.Factory subtitleParserFactory) {
|
||||||
this.extractorFactory = extractorFactory;
|
this.extractorFactory = extractorFactory;
|
||||||
this.playlistTracker = playlistTracker;
|
this.playlistTracker = playlistTracker;
|
||||||
this.dataSourceFactory = dataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
|
|
@ -164,6 +167,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
sampleStreamWrappers = new HlsSampleStreamWrapper[0];
|
sampleStreamWrappers = new HlsSampleStreamWrapper[0];
|
||||||
enabledSampleStreamWrappers = new HlsSampleStreamWrapper[0];
|
enabledSampleStreamWrappers = new HlsSampleStreamWrapper[0];
|
||||||
manifestUrlIndicesPerWrapper = new int[0][];
|
manifestUrlIndicesPerWrapper = new int[0][];
|
||||||
|
this.subtitleParserFactory = subtitleParserFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void release() {
|
public void release() {
|
||||||
|
|
@ -517,21 +521,43 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
for (int i = 0; i < subtitleRenditions.size(); i++) {
|
for (int i = 0; i < subtitleRenditions.size(); i++) {
|
||||||
Rendition subtitleRendition = subtitleRenditions.get(i);
|
Rendition subtitleRendition = subtitleRenditions.get(i);
|
||||||
String sampleStreamWrapperUid = "subtitle:" + i + ":" + subtitleRendition.name;
|
String sampleStreamWrapperUid = "subtitle:" + i + ":" + subtitleRendition.name;
|
||||||
|
// Format for HlsChunkSource to createExtractor with
|
||||||
|
Format originalSubtitleFormat = subtitleRendition.format;
|
||||||
HlsSampleStreamWrapper sampleStreamWrapper =
|
HlsSampleStreamWrapper sampleStreamWrapper =
|
||||||
buildSampleStreamWrapper(
|
buildSampleStreamWrapper(
|
||||||
sampleStreamWrapperUid,
|
sampleStreamWrapperUid,
|
||||||
C.TRACK_TYPE_TEXT,
|
C.TRACK_TYPE_TEXT,
|
||||||
new Uri[] {subtitleRendition.url},
|
new Uri[] {subtitleRendition.url},
|
||||||
new Format[] {subtitleRendition.format},
|
new Format[] {originalSubtitleFormat},
|
||||||
null,
|
null,
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
overridingDrmInitData,
|
overridingDrmInitData,
|
||||||
positionUs);
|
positionUs);
|
||||||
manifestUrlIndicesPerWrapper.add(new int[] {i});
|
manifestUrlIndicesPerWrapper.add(new int[] {i});
|
||||||
sampleStreamWrappers.add(sampleStreamWrapper);
|
sampleStreamWrappers.add(sampleStreamWrapper);
|
||||||
sampleStreamWrapper.prepareWithMultivariantPlaylistInfo(
|
if (subtitleParserFactory != null
|
||||||
new TrackGroup[] {new TrackGroup(sampleStreamWrapperUid, subtitleRendition.format)},
|
&& subtitleParserFactory.supportsFormat(originalSubtitleFormat)) {
|
||||||
/* primaryTrackGroupIndex= */ 0);
|
Format updatedSubtitleFormat =
|
||||||
|
originalSubtitleFormat
|
||||||
|
.buildUpon()
|
||||||
|
.setSampleMimeType(MimeTypes.APPLICATION_MEDIA3_CUES)
|
||||||
|
.setCueReplacementBehavior(
|
||||||
|
subtitleParserFactory.getCueReplacementBehavior(originalSubtitleFormat))
|
||||||
|
.setCodecs(
|
||||||
|
originalSubtitleFormat.sampleMimeType
|
||||||
|
+ (originalSubtitleFormat.codecs != null
|
||||||
|
? " " + originalSubtitleFormat.codecs
|
||||||
|
: ""))
|
||||||
|
.setSubsampleOffsetUs(Format.OFFSET_SAMPLE_RELATIVE)
|
||||||
|
.build();
|
||||||
|
sampleStreamWrapper.prepareWithMultivariantPlaylistInfo(
|
||||||
|
new TrackGroup[] {new TrackGroup(sampleStreamWrapperUid, updatedSubtitleFormat)},
|
||||||
|
/* primaryTrackGroupIndex= */ 0);
|
||||||
|
} else {
|
||||||
|
sampleStreamWrapper.prepareWithMultivariantPlaylistInfo(
|
||||||
|
new TrackGroup[] {new TrackGroup(sampleStreamWrapperUid, originalSubtitleFormat)},
|
||||||
|
/* primaryTrackGroupIndex= */ 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sampleStreamWrappers = sampleStreamWrappers.toArray(new HlsSampleStreamWrapper[0]);
|
this.sampleStreamWrappers = sampleStreamWrappers.toArray(new HlsSampleStreamWrapper[0]);
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,8 @@ 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;
|
||||||
|
|
@ -111,6 +113,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;
|
||||||
|
|
@ -196,6 +199,40 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether subtitles should be parsed as part of extraction (before the sample queue) or as
|
||||||
|
* part of rendering (after the sample queue). Defaults to false (i.e. subtitles will be parsed
|
||||||
|
* as part of rendering).
|
||||||
|
*
|
||||||
|
* <p>This method is experimental. Its default value may change, or it may be renamed or removed
|
||||||
|
* in a future release.
|
||||||
|
*
|
||||||
|
* <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 (probably wired up to a single method on
|
||||||
|
// DefaultMediaSourceFactory via the MediaSource.Factory interface).
|
||||||
|
public Factory experimentalParseSubtitlesDuringExtraction(
|
||||||
|
boolean parseSubtitlesDuringExtraction) {
|
||||||
|
if (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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the factory from which playlist parsers will be obtained.
|
* Sets the factory from which playlist parsers will be obtained.
|
||||||
*
|
*
|
||||||
|
|
@ -381,6 +418,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||||
mediaItem,
|
mediaItem,
|
||||||
hlsDataSourceFactory,
|
hlsDataSourceFactory,
|
||||||
extractorFactory,
|
extractorFactory,
|
||||||
|
subtitleParserFactory,
|
||||||
compositeSequenceableLoaderFactory,
|
compositeSequenceableLoaderFactory,
|
||||||
cmcdConfiguration,
|
cmcdConfiguration,
|
||||||
drmSessionManagerProvider.get(mediaItem),
|
drmSessionManagerProvider.get(mediaItem),
|
||||||
|
|
@ -412,6 +450,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||||
private final HlsPlaylistTracker playlistTracker;
|
private final HlsPlaylistTracker playlistTracker;
|
||||||
private final long elapsedRealTimeOffsetMs;
|
private final long elapsedRealTimeOffsetMs;
|
||||||
private final long timestampAdjusterInitializationTimeoutMs;
|
private final long timestampAdjusterInitializationTimeoutMs;
|
||||||
|
@Nullable private final SubtitleParser.Factory subtitleParserFactory;
|
||||||
|
|
||||||
private MediaItem.LiveConfiguration liveConfiguration;
|
private MediaItem.LiveConfiguration liveConfiguration;
|
||||||
@Nullable private TransferListener mediaTransferListener;
|
@Nullable private TransferListener mediaTransferListener;
|
||||||
|
|
@ -423,6 +462,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||||
MediaItem mediaItem,
|
MediaItem mediaItem,
|
||||||
HlsDataSourceFactory dataSourceFactory,
|
HlsDataSourceFactory dataSourceFactory,
|
||||||
HlsExtractorFactory extractorFactory,
|
HlsExtractorFactory extractorFactory,
|
||||||
|
@Nullable SubtitleParser.Factory subtitleParserFactory,
|
||||||
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
|
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
|
||||||
@Nullable CmcdConfiguration cmcdConfiguration,
|
@Nullable CmcdConfiguration cmcdConfiguration,
|
||||||
DrmSessionManager drmSessionManager,
|
DrmSessionManager drmSessionManager,
|
||||||
|
|
@ -437,6 +477,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||||
this.liveConfiguration = mediaItem.liveConfiguration;
|
this.liveConfiguration = mediaItem.liveConfiguration;
|
||||||
this.dataSourceFactory = dataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
this.extractorFactory = extractorFactory;
|
this.extractorFactory = extractorFactory;
|
||||||
|
this.subtitleParserFactory = subtitleParserFactory;
|
||||||
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
|
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
|
||||||
this.cmcdConfiguration = cmcdConfiguration;
|
this.cmcdConfiguration = cmcdConfiguration;
|
||||||
this.drmSessionManager = drmSessionManager;
|
this.drmSessionManager = drmSessionManager;
|
||||||
|
|
@ -511,7 +552,8 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||||
metadataType,
|
metadataType,
|
||||||
useSessionKeys,
|
useSessionKeys,
|
||||||
getPlayerId(),
|
getPlayerId(),
|
||||||
timestampAdjusterInitializationTimeoutMs);
|
timestampAdjusterInitializationTimeoutMs,
|
||||||
|
subtitleParserFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,8 @@ public final class HlsMediaPeriodTest {
|
||||||
HlsMediaSource.METADATA_TYPE_ID3,
|
HlsMediaSource.METADATA_TYPE_ID3,
|
||||||
/* useSessionKeys= */ false,
|
/* useSessionKeys= */ false,
|
||||||
PlayerId.UNSET,
|
PlayerId.UNSET,
|
||||||
/* timestampAdjusterInitializationTimeoutMs= */ 0);
|
/* timestampAdjusterInitializationTimeoutMs= */ 0,
|
||||||
|
/* subtitleParserFactory= */ null);
|
||||||
};
|
};
|
||||||
|
|
||||||
MediaPeriodAsserts.assertGetStreamKeysAndManifestFilterIntegration(
|
MediaPeriodAsserts.assertGetStreamKeysAndManifestFilterIntegration(
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,9 @@ import android.graphics.SurfaceTexture;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.Player;
|
import androidx.media3.common.Player;
|
||||||
|
import androidx.media3.datasource.DefaultDataSource;
|
||||||
import androidx.media3.exoplayer.ExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
|
import androidx.media3.exoplayer.hls.HlsMediaSource;
|
||||||
import androidx.media3.test.utils.CapturingRenderersFactory;
|
import androidx.media3.test.utils.CapturingRenderersFactory;
|
||||||
import androidx.media3.test.utils.DumpFileAsserts;
|
import androidx.media3.test.utils.DumpFileAsserts;
|
||||||
import androidx.media3.test.utils.FakeClock;
|
import androidx.media3.test.utils.FakeClock;
|
||||||
|
|
@ -30,7 +32,6 @@ import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
|
||||||
import androidx.media3.test.utils.robolectric.TestPlayerRunHelper;
|
import androidx.media3.test.utils.robolectric.TestPlayerRunHelper;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
@ -44,14 +45,15 @@ public final class HlsPlaybackTest {
|
||||||
ShadowMediaCodecConfig.forAllSupportedMimeTypes();
|
ShadowMediaCodecConfig.forAllSupportedMimeTypes();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore(
|
|
||||||
"Disabled until subtitles are reliably asserted in robolectric tests [internal b/174661563].")
|
|
||||||
public void webvttSubtitles() throws Exception {
|
public void webvttSubtitles() throws Exception {
|
||||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||||
CapturingRenderersFactory capturingRenderersFactory =
|
CapturingRenderersFactory capturingRenderersFactory =
|
||||||
new CapturingRenderersFactory(applicationContext);
|
new CapturingRenderersFactory(applicationContext);
|
||||||
ExoPlayer player =
|
ExoPlayer player =
|
||||||
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
||||||
|
.setMediaSourceFactory(
|
||||||
|
new HlsMediaSource.Factory(new DefaultDataSource.Factory(applicationContext))
|
||||||
|
.experimentalParseSubtitlesDuringExtraction(true))
|
||||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||||
.build();
|
.build();
|
||||||
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue