Default to parse subtitles while extracting, instead of while rendering
To override this change, and go back to parsing during rendering, apps must make two method calls: 1. `MediaSource.Factory.experimentalParseSubtitlesDuringExtraction(false)` 2. `TextRenderer.experimentalSetLegacyDecodingEnabled(true)` PiperOrigin-RevId: 634262798
This commit is contained in:
parent
5c6f48ecaf
commit
0352db9a37
@ -67,6 +67,21 @@
|
||||
* Text:
|
||||
* Fix issue where subtitles starting before a seek position are skipped.
|
||||
This issue was only introduced in Media3 1.4.0-alpha01.
|
||||
* Change default subtitle parsing behavior so it happens during extraction
|
||||
instead of during rendering (see
|
||||
[ExoPlayer's architecture diagram](https://developer.android.com/media/media3/exoplayer/glossary#exoplayer)
|
||||
for the difference between extraction and rendering).
|
||||
* This change can be overridden by calling **both**
|
||||
`MediaSource.Factory.experimentalParseSubtitlesDuringExtraction(false)`
|
||||
and `TextRenderer.experimentalSetLegacyDecodingEnabled(true)`. See
|
||||
the
|
||||
[docs on customization](https://developer.android.com/media/media3/exoplayer/customization)
|
||||
for how to plumb these components into an `ExoPlayer` instance.
|
||||
These methods (and all support for legacy subtitle decoding) will be
|
||||
removed in a future release.
|
||||
* Apps with custom `SubtitleDecoder` implementations need to update
|
||||
them to implement `SubtitleParser` instead (and
|
||||
`SubtitleParser.Factory` instead of `SubtitleDecoderFactory`).
|
||||
* Metadata:
|
||||
* Fix mapping of MP4 to ID3 sort tags. Previously the 'album sort'
|
||||
(`soal`), 'artist sort' (`soar`) and 'album artist sort' (`soaa`) MP4
|
||||
|
@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Looper;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaItem.SubtitleConfiguration;
|
||||
@ -31,6 +32,8 @@ import androidx.media3.common.text.CueGroup;
|
||||
import androidx.media3.common.util.ConditionVariable;
|
||||
import androidx.media3.exoplayer.source.ClippingMediaSource;
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||
import androidx.media3.exoplayer.text.TextOutput;
|
||||
import androidx.media3.exoplayer.text.TextRenderer;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import java.util.ArrayList;
|
||||
@ -86,14 +89,8 @@ public final class ClippedPlaybackTest {
|
||||
getInstrumentation()
|
||||
.runOnMainSync(
|
||||
() -> {
|
||||
Context context = getInstrumentation().getContext();
|
||||
player.set(
|
||||
new ExoPlayer.Builder(context)
|
||||
.setMediaSourceFactory(
|
||||
new DefaultMediaSourceFactory(context)
|
||||
.experimentalParseSubtitlesDuringExtraction(
|
||||
parseSubtitlesDuringExtraction))
|
||||
.build());
|
||||
buildPlayer(getInstrumentation().getContext(), parseSubtitlesDuringExtraction));
|
||||
player.get().addListener(textCapturer);
|
||||
player.get().setMediaItem(mediaItem);
|
||||
player.get().prepare();
|
||||
@ -138,14 +135,8 @@ public final class ClippedPlaybackTest {
|
||||
getInstrumentation()
|
||||
.runOnMainSync(
|
||||
() -> {
|
||||
Context context = getInstrumentation().getContext();
|
||||
player.set(
|
||||
new ExoPlayer.Builder(context)
|
||||
.setMediaSourceFactory(
|
||||
new DefaultMediaSourceFactory(context)
|
||||
.experimentalParseSubtitlesDuringExtraction(
|
||||
parseSubtitlesDuringExtraction))
|
||||
.build());
|
||||
buildPlayer(getInstrumentation().getContext(), parseSubtitlesDuringExtraction));
|
||||
player.get().addListener(textCapturer);
|
||||
player.get().setMediaItems(mediaItems);
|
||||
player.get().prepare();
|
||||
@ -163,6 +154,33 @@ public final class ClippedPlaybackTest {
|
||||
.isEqualTo("This is the first subtitle.");
|
||||
}
|
||||
|
||||
// Using deprecated TextRenderer.experimentalSetLegacyDecodingEnabled() and
|
||||
// MediaSource.Factory.experimentalParseSubtitlesDuringExtraction() methods to ensure legacy
|
||||
// subtitle handling keeps working.
|
||||
@SuppressWarnings("deprecation")
|
||||
private static ExoPlayer buildPlayer(Context context, boolean parseSubtitlesDuringExtraction) {
|
||||
return new ExoPlayer.Builder(context)
|
||||
.setRenderersFactory(
|
||||
new DefaultRenderersFactory(context) {
|
||||
|
||||
@Override
|
||||
protected void buildTextRenderers(
|
||||
Context context,
|
||||
TextOutput output,
|
||||
Looper outputLooper,
|
||||
@ExtensionRendererMode int extensionRendererMode,
|
||||
ArrayList<Renderer> out) {
|
||||
super.buildTextRenderers(context, output, outputLooper, extensionRendererMode, out);
|
||||
((TextRenderer) Iterables.getLast(out))
|
||||
.experimentalSetLegacyDecodingEnabled(!parseSubtitlesDuringExtraction);
|
||||
}
|
||||
})
|
||||
.setMediaSourceFactory(
|
||||
new DefaultMediaSourceFactory(context)
|
||||
.experimentalParseSubtitlesDuringExtraction(parseSubtitlesDuringExtraction))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static void playWhenLoadingIsDone(Player player) {
|
||||
AtomicBoolean loadingStarted = new AtomicBoolean(false);
|
||||
player.addListener(
|
||||
|
@ -187,10 +187,12 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
liveMaxOffsetMs = C.TIME_UNSET;
|
||||
liveMinSpeed = C.RATE_UNSET;
|
||||
liveMaxSpeed = C.RATE_UNSET;
|
||||
parseSubtitlesDuringExtraction = true;
|
||||
}
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
@Override
|
||||
public DefaultMediaSourceFactory experimentalParseSubtitlesDuringExtraction(
|
||||
boolean parseSubtitlesDuringExtraction) {
|
||||
@ -620,6 +622,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
this.subtitleParserFactory = subtitleParserFactory;
|
||||
mediaSourceFactorySuppliers = new HashMap<>();
|
||||
mediaSourceFactories = new HashMap<>();
|
||||
parseSubtitlesDuringExtraction = true;
|
||||
}
|
||||
|
||||
public @C.ContentType int[] getSupportedTypes() {
|
||||
|
@ -102,24 +102,25 @@ public interface MediaSource {
|
||||
/**
|
||||
* 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).
|
||||
* {@code true} (i.e. subtitles will be parsed during extraction).
|
||||
*
|
||||
* <p>This method is experimental and will be renamed or removed in a future release.
|
||||
*
|
||||
* @deprecated This method (and all support for 'legacy' subtitle decoding during rendering)
|
||||
* will be removed in a future release.
|
||||
* @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.
|
||||
@UnstableApi
|
||||
@Deprecated
|
||||
default Factory experimentalParseSubtitlesDuringExtraction(
|
||||
boolean parseSubtitlesDuringExtraction) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link SubtitleParser.Factory} to be used for parsing subtitles during extraction if
|
||||
* {@link #experimentalParseSubtitlesDuringExtraction} is enabled.
|
||||
* Sets the {@link SubtitleParser.Factory} to be used for parsing subtitles during extraction.
|
||||
*
|
||||
* @param subtitleParserFactory The {@link SubtitleParser.Factory} for parsing subtitles during
|
||||
* extraction.
|
||||
|
@ -161,7 +161,7 @@ public final class TextRenderer extends BaseRenderer implements Callback {
|
||||
finalStreamEndPositionUs = C.TIME_UNSET;
|
||||
outputStreamOffsetUs = C.TIME_UNSET;
|
||||
lastRendererPositionUs = C.TIME_UNSET;
|
||||
legacyDecodingEnabled = true;
|
||||
legacyDecodingEnabled = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -277,11 +277,15 @@ public final class TextRenderer extends BaseRenderer implements Callback {
|
||||
* MimeTypes#APPLICATION_MEDIA3_CUES} (which have been parsed from their original format during
|
||||
* extraction), and will throw an exception if passed data of a different type.
|
||||
*
|
||||
* <p>This is enabled by default.
|
||||
* <p>This is disabled by default.
|
||||
*
|
||||
* <p>This method is experimental. It may change behavior, be renamed, or removed in a future
|
||||
* release.
|
||||
*
|
||||
* @deprecated This method (and all support for 'legacy' subtitle decoding during rendering) will
|
||||
* be removed in a future release.
|
||||
*/
|
||||
@Deprecated
|
||||
public void experimentalSetLegacyDecodingEnabled(boolean legacyDecodingEnabled) {
|
||||
this.legacyDecodingEnabled = legacyDecodingEnabled;
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||
import androidx.media3.extractor.DefaultExtractorsFactory;
|
||||
import androidx.media3.test.utils.CapturingRenderersFactory;
|
||||
import androidx.media3.test.utils.DumpFileAsserts;
|
||||
import androidx.media3.test.utils.FakeClock;
|
||||
@ -66,11 +65,8 @@ public final class MkvPlaybackTest {
|
||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||
CapturingRenderersFactory capturingRenderersFactory =
|
||||
new CapturingRenderersFactory(applicationContext);
|
||||
// TODO: b/289916598 - Remove this when transcoding is the default.
|
||||
DefaultExtractorsFactory extractorsFactory =
|
||||
new DefaultExtractorsFactory().setTextTrackTranscodingEnabled(true);
|
||||
DefaultMediaSourceFactory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(applicationContext, extractorsFactory);
|
||||
new DefaultMediaSourceFactory(applicationContext);
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory, mediaSourceFactory)
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
|
@ -28,8 +28,6 @@ import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.test.utils.CapturingRenderersFactory;
|
||||
import androidx.media3.test.utils.DumpFileAsserts;
|
||||
import androidx.media3.test.utils.FakeClock;
|
||||
@ -107,13 +105,9 @@ public final class PlaylistPlaybackTest {
|
||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||
CapturingRenderersFactory capturingRenderersFactory =
|
||||
new CapturingRenderersFactory(applicationContext);
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(applicationContext)
|
||||
.experimentalParseSubtitlesDuringExtraction(true);
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.build();
|
||||
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
|
||||
player.setVideoSurface(surface);
|
||||
|
@ -22,7 +22,6 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import android.content.Context;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.net.Uri;
|
||||
import android.os.Looper;
|
||||
import android.view.Surface;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
@ -35,11 +34,9 @@ import androidx.media3.exoplayer.DefaultLoadControl;
|
||||
import androidx.media3.exoplayer.DefaultRenderersFactory;
|
||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.Renderer;
|
||||
import androidx.media3.exoplayer.RenderersFactory;
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||
import androidx.media3.exoplayer.source.MediaSource;
|
||||
import androidx.media3.exoplayer.text.TextOutput;
|
||||
import androidx.media3.exoplayer.text.TextRenderer;
|
||||
import androidx.media3.test.utils.CapturingRenderersFactory;
|
||||
import androidx.media3.test.utils.DumpFileAsserts;
|
||||
@ -50,8 +47,6 @@ import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
|
||||
import androidx.media3.test.utils.robolectric.TestPlayerRunHelper;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.junit.Rule;
|
||||
@ -78,13 +73,9 @@ public class WebvttPlaybackTest {
|
||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||
CapturingRenderersFactory capturingRenderersFactory =
|
||||
new CapturingRenderersFactory(applicationContext);
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(applicationContext)
|
||||
.experimentalParseSubtitlesDuringExtraction(true);
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.build();
|
||||
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
|
||||
player.setVideoSurface(surface);
|
||||
@ -120,13 +111,9 @@ public class WebvttPlaybackTest {
|
||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||
CapturingRenderersFactory capturingRenderersFactory =
|
||||
new CapturingRenderersFactory(applicationContext);
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(applicationContext)
|
||||
.experimentalParseSubtitlesDuringExtraction(true);
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.setLoadControl(
|
||||
new DefaultLoadControl.Builder()
|
||||
.setBackBuffer(
|
||||
@ -167,11 +154,21 @@ public class WebvttPlaybackTest {
|
||||
applicationContext, playbackOutput, "playbackdumps/webvtt/" + inputFile + ".seek.dump");
|
||||
}
|
||||
|
||||
// Using deprecated TextRenderer.experimentalSetLegacyDecodingEnabled() and
|
||||
// MediaSource.Factory.experimentalParseSubtitlesDuringExtraction() methods to ensure legacy
|
||||
// subtitle handling keeps working.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void test_legacyParseInRenderer() throws Exception {
|
||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||
CapturingRenderersFactory capturingRenderersFactory =
|
||||
new CapturingRenderersFactory(applicationContext);
|
||||
new CapturingRenderersFactory(applicationContext)
|
||||
.setTextRendererFactory(
|
||||
(textOutput, outputLooper) -> {
|
||||
TextRenderer renderer = new TextRenderer(textOutput, outputLooper);
|
||||
renderer.experimentalSetLegacyDecodingEnabled(true);
|
||||
return renderer;
|
||||
});
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(applicationContext)
|
||||
.experimentalParseSubtitlesDuringExtraction(false);
|
||||
@ -215,11 +212,21 @@ public class WebvttPlaybackTest {
|
||||
applicationContext, playbackOutput, "playbackdumps/webvtt/" + inputFile + ".dump");
|
||||
}
|
||||
|
||||
// Using deprecated TextRenderer.experimentalSetLegacyDecodingEnabled() and
|
||||
// MediaSource.Factory.experimentalParseSubtitlesDuringExtraction() methods to ensure legacy
|
||||
// subtitle handling keeps working.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void test_legacyParseInRendererWithSeek() throws Exception {
|
||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||
CapturingRenderersFactory capturingRenderersFactory =
|
||||
new CapturingRenderersFactory(applicationContext);
|
||||
new CapturingRenderersFactory(applicationContext)
|
||||
.setTextRendererFactory(
|
||||
(textOutput, outputLooper) -> {
|
||||
TextRenderer renderer = new TextRenderer(textOutput, outputLooper);
|
||||
renderer.experimentalSetLegacyDecodingEnabled(true);
|
||||
return renderer;
|
||||
});
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(applicationContext)
|
||||
.experimentalParseSubtitlesDuringExtraction(false);
|
||||
@ -278,22 +285,12 @@ public class WebvttPlaybackTest {
|
||||
applicationContext, playbackOutput, "playbackdumps/webvtt/" + inputFile + ".seek.dump");
|
||||
}
|
||||
|
||||
// Deliberately configuring legacy subtitle handling to check unconfigured TextRenderer fails.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void textRendererDoesntSupportLegacyDecoding_playbackFails() throws Exception {
|
||||
public void textRenderer_doesntSupportLegacyDecodingByDefault_playbackFails() throws Exception {
|
||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||
RenderersFactory renderersFactory =
|
||||
new DefaultRenderersFactory(applicationContext) {
|
||||
@Override
|
||||
protected void buildTextRenderers(
|
||||
Context context,
|
||||
TextOutput output,
|
||||
Looper outputLooper,
|
||||
@ExtensionRendererMode int extensionRendererMode,
|
||||
ArrayList<Renderer> out) {
|
||||
super.buildTextRenderers(context, output, outputLooper, extensionRendererMode, out);
|
||||
((TextRenderer) Iterables.getLast(out)).experimentalSetLegacyDecodingEnabled(false);
|
||||
}
|
||||
};
|
||||
RenderersFactory renderersFactory = new DefaultRenderersFactory(applicationContext);
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(applicationContext)
|
||||
.experimentalParseSubtitlesDuringExtraction(false);
|
||||
|
@ -163,6 +163,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
fallbackTargetLiveOffsetMs = DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS;
|
||||
minLiveStartPositionUs = MIN_LIVE_DEFAULT_START_POSITION_US;
|
||||
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
|
||||
experimentalParseSubtitlesDuringExtraction(true);
|
||||
}
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
@ -205,6 +206,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
@CanIgnoreReturnValue
|
||||
public Factory experimentalParseSubtitlesDuringExtraction(
|
||||
boolean parseSubtitlesDuringExtraction) {
|
||||
|
@ -58,9 +58,6 @@ public final class DashPlaybackTest {
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
.setMediaSourceFactory(
|
||||
new DashMediaSource.Factory(new DefaultDataSource.Factory(applicationContext))
|
||||
.experimentalParseSubtitlesDuringExtraction(true))
|
||||
.build();
|
||||
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
|
||||
@ -87,9 +84,6 @@ public final class DashPlaybackTest {
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
.setMediaSourceFactory(
|
||||
new DashMediaSource.Factory(new DefaultDataSource.Factory(applicationContext))
|
||||
.experimentalParseSubtitlesDuringExtraction(true))
|
||||
.build();
|
||||
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
|
||||
@ -116,9 +110,6 @@ public final class DashPlaybackTest {
|
||||
new CapturingRenderersFactory(applicationContext);
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
||||
.setMediaSourceFactory(
|
||||
new DashMediaSource.Factory(new DefaultDataSource.Factory(applicationContext))
|
||||
.experimentalParseSubtitlesDuringExtraction(true))
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
.build();
|
||||
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||
@ -145,9 +136,6 @@ public final class DashPlaybackTest {
|
||||
new CapturingRenderersFactory(applicationContext);
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
||||
.setMediaSourceFactory(
|
||||
new DashMediaSource.Factory(new DefaultDataSource.Factory(applicationContext))
|
||||
.experimentalParseSubtitlesDuringExtraction(true))
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
.build();
|
||||
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||
@ -171,6 +159,9 @@ public final class DashPlaybackTest {
|
||||
* This test and {@link #cea608_parseDuringExtraction()} use the same output dump file, to
|
||||
* demonstrate the flag has no effect on the resulting subtitles.
|
||||
*/
|
||||
// Using deprecated MediaSource.Factory.experimentalParseSubtitlesDuringExtraction() method to
|
||||
// ensure legacy subtitle handling keeps working.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void cea608_parseDuringRendering() throws Exception {
|
||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||
@ -204,6 +195,9 @@ public final class DashPlaybackTest {
|
||||
* This test and {@link #cea608_parseDuringRendering()} use the same output dump file, to
|
||||
* demonstrate the flag has no effect on the resulting subtitles.
|
||||
*/
|
||||
// Explicitly enable parsing during extraction (even though a) it's the default and b) currently
|
||||
// all CEA-608 parsing happens during rendering) to make this test clearer & more future-proof.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void cea608_parseDuringExtraction() throws Exception {
|
||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||
|
@ -169,6 +169,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
metadataType = METADATA_TYPE_ID3;
|
||||
elapsedRealTimeOffsetMs = C.TIME_UNSET;
|
||||
allowChunklessPreparation = true;
|
||||
experimentalParseSubtitlesDuringExtraction(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -206,6 +207,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
@CanIgnoreReturnValue
|
||||
public Factory experimentalParseSubtitlesDuringExtraction(
|
||||
boolean parseSubtitlesDuringExtraction) {
|
||||
|
@ -53,9 +53,6 @@ public final class HlsPlaybackTest {
|
||||
new CapturingRenderersFactory(applicationContext);
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
||||
.setMediaSourceFactory(
|
||||
new HlsMediaSource.Factory(new DefaultDataSource.Factory(applicationContext))
|
||||
.experimentalParseSubtitlesDuringExtraction(true))
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
.build();
|
||||
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||
@ -79,9 +76,6 @@ public final class HlsPlaybackTest {
|
||||
new CapturingRenderersFactory(applicationContext);
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
||||
.setMediaSourceFactory(
|
||||
new HlsMediaSource.Factory(new DefaultDataSource.Factory(applicationContext))
|
||||
.experimentalParseSubtitlesDuringExtraction(true))
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
.build();
|
||||
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||
@ -102,6 +96,9 @@ public final class HlsPlaybackTest {
|
||||
* This test and {@link #cea608_parseDuringExtraction()} use the same output dump file, to
|
||||
* demonstrate the flag has no effect on the resulting subtitles.
|
||||
*/
|
||||
// Using deprecated MediaSource.Factory.experimentalParseSubtitlesDuringExtraction() method to
|
||||
// ensure legacy subtitle handling keeps working.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void cea608_parseDuringRendering() throws Exception {
|
||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||
@ -131,6 +128,9 @@ public final class HlsPlaybackTest {
|
||||
* This test and {@link #cea608_parseDuringRendering()} use the same output dump file, to
|
||||
* demonstrate the flag has no effect on the resulting subtitles.
|
||||
*/
|
||||
// Explicitly enable parsing during extraction (even though a) it's the default and b) currently
|
||||
// all CEA-608 parsing happens during rendering) to make this test clearer & more future-proof.
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void cea608_parseDuringExtraction() throws Exception {
|
||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||
@ -164,9 +164,6 @@ public final class HlsPlaybackTest {
|
||||
new CapturingRenderersFactory(applicationContext);
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
||||
.setMediaSourceFactory(
|
||||
new HlsMediaSource.Factory(new DefaultDataSource.Factory(applicationContext))
|
||||
.experimentalParseSubtitlesDuringExtraction(true))
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
.setLoadControl(
|
||||
new DefaultLoadControl.Builder()
|
||||
|
@ -139,6 +139,7 @@ public final class SsMediaSource extends BaseMediaSource
|
||||
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
||||
livePresentationDelayMs = DEFAULT_LIVE_PRESENTATION_DELAY_MS;
|
||||
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
|
||||
experimentalParseSubtitlesDuringExtraction(true);
|
||||
}
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
@ -161,6 +162,7 @@ public final class SsMediaSource extends BaseMediaSource
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
@CanIgnoreReturnValue
|
||||
public Factory experimentalParseSubtitlesDuringExtraction(
|
||||
boolean parseSubtitlesDuringExtraction) {
|
||||
|
@ -103,6 +103,7 @@ public class SsMediaSourceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation") // Testing deprecated method
|
||||
public void
|
||||
setExperimentalParseSubtitlesDuringExtraction_withNonDefaultChunkSourceFactory_setSucceeds() {
|
||||
SsMediaSource.Factory ssMediaSourceFactory =
|
||||
@ -113,6 +114,7 @@ public class SsMediaSourceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation") // Testing deprecated method
|
||||
public void
|
||||
setExperimentalParseSubtitlesDuringExtraction_withDefaultChunkSourceFactory_setSucceeds() {
|
||||
SsMediaSource.Factory ssMediaSourceFactory =
|
||||
|
@ -161,6 +161,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
tsMode = TsExtractor.MODE_SINGLE_PMT;
|
||||
tsTimestampSearchBytes = TsExtractor.DEFAULT_TIMESTAMP_SEARCH_BYTES;
|
||||
subtitleParserFactory = new DefaultSubtitleParserFactory();
|
||||
textTrackTranscodingEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -361,7 +362,8 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #experimentalSetTextTrackTranscodingEnabled(boolean)} instead.
|
||||
* @deprecated This method (and all support for 'legacy' subtitle decoding during rendering) will
|
||||
* be removed in a future release.
|
||||
*/
|
||||
@Deprecated
|
||||
@CanIgnoreReturnValue
|
||||
@ -370,6 +372,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
return experimentalSetTextTrackTranscodingEnabled(textTrackTranscodingEnabled);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public synchronized DefaultExtractorsFactory experimentalSetTextTrackTranscodingEnabled(
|
||||
boolean textTrackTranscodingEnabled) {
|
||||
|
@ -37,15 +37,17 @@ public interface ExtractorsFactory {
|
||||
* Enables transcoding of text track samples to {@link MimeTypes#APPLICATION_MEDIA3_CUES} before
|
||||
* the data is emitted to {@link TrackOutput}.
|
||||
*
|
||||
* <p>Transcoding is disabled by default.
|
||||
* <p>Transcoding is enabled by default.
|
||||
*
|
||||
* <p>This method is experimental and will be renamed or removed in a future release.
|
||||
*
|
||||
* @param textTrackTranscodingEnabled Whether to enable transcoding.
|
||||
* @return The factory, for convenience.
|
||||
* @deprecated This method (and all support for 'legacy' subtitle decoding during rendering) will
|
||||
* be removed in a future release.
|
||||
*/
|
||||
// TODO: b/289916598 - Flip this to default to enabled and deprecate it.
|
||||
@CanIgnoreReturnValue
|
||||
@Deprecated
|
||||
default ExtractorsFactory experimentalSetTextTrackTranscodingEnabled(
|
||||
boolean textTrackTranscodingEnabled) {
|
||||
return this;
|
||||
|
@ -137,11 +137,23 @@ public final class DefaultExtractorsFactoryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subtitleTranscoding_notEnabledByDefault() {
|
||||
public void subtitleTranscoding_enabledByDefault() {
|
||||
DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
|
||||
|
||||
Extractor[] extractors = defaultExtractorsFactory.createExtractors();
|
||||
|
||||
assertThat(stream(extractors).map(Object::getClass))
|
||||
.contains(SubtitleTranscodingExtractor.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Testing legacy subtitle handling
|
||||
@Test
|
||||
public void subtitleTranscoding_noExtractorWrappingIfDisabled() {
|
||||
DefaultExtractorsFactory defaultExtractorsFactory =
|
||||
new DefaultExtractorsFactory().setTextTrackTranscodingEnabled(false);
|
||||
|
||||
Extractor[] extractors = defaultExtractorsFactory.createExtractors();
|
||||
|
||||
assertThat(stream(extractors).map(Object::getClass))
|
||||
.doesNotContain(SubtitleTranscodingExtractor.class);
|
||||
}
|
||||
|
@ -23,11 +23,13 @@ import android.media.MediaCodec;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.PersistableBundle;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.decoder.CryptoInfo;
|
||||
import androidx.media3.exoplayer.DefaultRenderersFactory;
|
||||
@ -50,6 +52,7 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
@ -72,7 +75,9 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa
|
||||
private final CapturingMediaCodecAdapter.Factory mediaCodecAdapterFactory;
|
||||
private final CapturingAudioSink audioSink;
|
||||
private final CapturingImageOutput imageOutput;
|
||||
|
||||
private ImageDecoder.Factory imageDecoderFactory;
|
||||
private TextRendererFactory textRendererFactory;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
@ -85,6 +90,7 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa
|
||||
this.audioSink = new CapturingAudioSink(new DefaultAudioSink.Builder(context).build());
|
||||
this.imageOutput = new CapturingImageOutput();
|
||||
this.imageDecoderFactory = ImageDecoder.Factory.DEFAULT;
|
||||
this.textRendererFactory = TextRenderer::new;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,6 +105,18 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the factory for {@link Renderer} instances that handle {@link C#TRACK_TYPE_TEXT} tracks.
|
||||
*
|
||||
* @param textRendererFactory The {@link TextRendererFactory}.
|
||||
* @return This factory, for convenience.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public CapturingRenderersFactory setTextRendererFactory(TextRendererFactory textRendererFactory) {
|
||||
this.textRendererFactory = textRendererFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Renderer[] createRenderers(
|
||||
Handler eventHandler,
|
||||
@ -146,7 +164,7 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa
|
||||
eventHandler,
|
||||
audioRendererEventListener,
|
||||
audioSink));
|
||||
renderers.add(new TextRenderer(textRendererOutput, eventHandler.getLooper()));
|
||||
renderers.add(textRendererFactory.create(textRendererOutput, eventHandler.getLooper()));
|
||||
renderers.add(new MetadataRenderer(metadataRendererOutput, eventHandler.getLooper()));
|
||||
renderers.add(new ImageRenderer(imageDecoderFactory, imageOutput));
|
||||
|
||||
@ -160,6 +178,18 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa
|
||||
imageOutput.dump(dumper);
|
||||
}
|
||||
|
||||
/** A factory for {@link Renderer} instances that handle {@link C#TRACK_TYPE_TEXT} tracks. */
|
||||
public interface TextRendererFactory {
|
||||
|
||||
/**
|
||||
* Creates a new {@link Renderer} instance for a {@link C#TRACK_TYPE_TEXT} track.
|
||||
*
|
||||
* @param textOutput A {@link TextOutput} to handle the parsed subtitles.
|
||||
* @param outputLooper The looper used to invoke {@code textOutput}.
|
||||
*/
|
||||
Renderer create(TextOutput textOutput, Looper outputLooper);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link MediaCodecAdapter} that captures interactions and exposes them for test assertions via
|
||||
* {@link Dumper.Dumpable}.
|
||||
|
Loading…
x
Reference in New Issue
Block a user