diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java index 1fb35b81c2..70959aa7c1 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java @@ -125,6 +125,7 @@ public final class TextRenderer extends BaseRenderer implements Callback { private long outputStreamOffsetUs; private long lastRendererPositionUs; private long finalStreamEndPositionUs; + private boolean legacyDecodingEnabled; /** * @param output The output. @@ -163,6 +164,7 @@ public final class TextRenderer extends BaseRenderer implements Callback { finalStreamEndPositionUs = C.TIME_UNSET; outputStreamOffsetUs = C.TIME_UNSET; lastRendererPositionUs = C.TIME_UNSET; + legacyDecodingEnabled = true; } @Override @@ -172,6 +174,10 @@ public final class TextRenderer extends BaseRenderer implements Callback { @Override public @Capabilities int supportsFormat(Format format) { + // TODO: b/289983417 - Return UNSUPPORTED for non-media3-queues once we stop supporting them + // completely. In the meantime, we return SUPPORTED here and then throw later if + // legacyDecodingEnabled is false (when receiving the first Format or sample). This ensures + // apps are aware (via the playback failure) they're using a legacy/deprecated code path. if (isCuesWithTiming(format) || subtitleDecoderFactory.supportsFormat(format)) { return RendererCapabilities.create( format.cryptoType == C.CRYPTO_TYPE_NONE ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_DRM); @@ -206,6 +212,7 @@ public final class TextRenderer extends BaseRenderer implements Callback { outputStreamOffsetUs = offsetUs; streamFormat = formats[0]; if (!isCuesWithTiming(streamFormat)) { + assertLegacyDecodingEnabledIfRequired(); if (subtitleDecoder != null) { decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM; } else { @@ -258,10 +265,30 @@ public final class TextRenderer extends BaseRenderer implements Callback { checkNotNull(cuesResolver); renderFromCuesWithTiming(positionUs); } else { + assertLegacyDecodingEnabledIfRequired(); renderFromSubtitles(positionUs); } } + /** + * Sets whether to decode subtitle data during rendering. + * + *
If this is enabled, then the {@link SubtitleDecoderFactory} passed to the constructor is + * used to decode subtitle data during rendering. + * + *
If this is disabled this text renderer can only handle tracks with MIME type {@link + * 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. + * + *
This is enabled by default. + * + *
This method is experimental. It may change behavior, be renamed, or removed in a future
+ * release.
+ */
+ public void experimentalSetLegacyDecodingEnabled(boolean legacyDecodingEnabled) {
+ this.legacyDecodingEnabled = legacyDecodingEnabled;
+ }
+
@RequiresNonNull("this.cuesResolver")
private void renderFromCuesWithTiming(long positionUs) {
boolean outputNeedsUpdating = readAndDecodeCuesWithTiming(positionUs);
@@ -558,7 +585,22 @@ public final class TextRenderer extends BaseRenderer implements Callback {
return positionUs - outputStreamOffsetUs;
}
+ @RequiresNonNull("streamFormat")
+ private void assertLegacyDecodingEnabledIfRequired() {
+ checkState(
+ legacyDecodingEnabled
+ || Objects.equals(streamFormat.sampleMimeType, MimeTypes.APPLICATION_CEA608)
+ || Objects.equals(streamFormat.sampleMimeType, MimeTypes.APPLICATION_MP4CEA608)
+ || Objects.equals(streamFormat.sampleMimeType, MimeTypes.APPLICATION_CEA708),
+ "Legacy decoding is disabled, can't handle "
+ + streamFormat.sampleMimeType
+ + " samples (expected "
+ + MimeTypes.APPLICATION_MEDIA3_CUES
+ + ").");
+ }
+
/** Returns whether {@link Format#sampleMimeType} is {@link MimeTypes#APPLICATION_MEDIA3_CUES}. */
+ @SideEffectFree
private static boolean isCuesWithTiming(Format format) {
return Objects.equals(format.sampleMimeType, MimeTypes.APPLICATION_MEDIA3_CUES);
}
diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/WebvttPlaybackTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/WebvttPlaybackTest.java
index 8a53df893e..cc422e55f1 100644
--- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/WebvttPlaybackTest.java
+++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/WebvttPlaybackTest.java
@@ -15,17 +15,26 @@
*/
package androidx.media3.exoplayer.e2etest;
+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;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.Player;
+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;
import androidx.media3.test.utils.FakeClock;
@@ -34,6 +43,8 @@ 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 org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -92,4 +103,55 @@ public class WebvttPlaybackTest {
DumpFileAsserts.assertOutput(
applicationContext, playbackOutput, "playbackdumps/webvtt/" + inputFile + ".dump");
}
+
+ @Test
+ public void textRendererDoesntSupportLegacyDecoding_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