Skip to content

Commit 80bfa81

Browse files
icbakercopybara-github
authored andcommitted
Add method to TextRenderer to control whether decoding is done or not
When we default to 'parse during extraction', we will flip the default of this, to ensure that apps know they are using an incompatible/deprecated flow for subtitle handling. PiperOrigin-RevId: 599109304
1 parent 2c8ba50 commit 80bfa81

File tree

2 files changed

+104
-0
lines changed

2 files changed

+104
-0
lines changed

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ public final class TextRenderer extends BaseRenderer implements Callback {
125125
private long outputStreamOffsetUs;
126126
private long lastRendererPositionUs;
127127
private long finalStreamEndPositionUs;
128+
private boolean legacyDecodingEnabled;
128129

129130
/**
130131
* @param output The output.
@@ -163,6 +164,7 @@ public TextRenderer(
163164
finalStreamEndPositionUs = C.TIME_UNSET;
164165
outputStreamOffsetUs = C.TIME_UNSET;
165166
lastRendererPositionUs = C.TIME_UNSET;
167+
legacyDecodingEnabled = true;
166168
}
167169

168170
@Override
@@ -172,6 +174,10 @@ public String getName() {
172174

173175
@Override
174176
public @Capabilities int supportsFormat(Format format) {
177+
// TODO: b/289983417 - Return UNSUPPORTED for non-media3-queues once we stop supporting them
178+
// completely. In the meantime, we return SUPPORTED here and then throw later if
179+
// legacyDecodingEnabled is false (when receiving the first Format or sample). This ensures
180+
// apps are aware (via the playback failure) they're using a legacy/deprecated code path.
175181
if (isCuesWithTiming(format) || subtitleDecoderFactory.supportsFormat(format)) {
176182
return RendererCapabilities.create(
177183
format.cryptoType == C.CRYPTO_TYPE_NONE ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_DRM);
@@ -206,6 +212,7 @@ protected void onStreamChanged(
206212
outputStreamOffsetUs = offsetUs;
207213
streamFormat = formats[0];
208214
if (!isCuesWithTiming(streamFormat)) {
215+
assertLegacyDecodingEnabledIfRequired();
209216
if (subtitleDecoder != null) {
210217
decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM;
211218
} else {
@@ -258,10 +265,30 @@ public void render(long positionUs, long elapsedRealtimeUs) {
258265
checkNotNull(cuesResolver);
259266
renderFromCuesWithTiming(positionUs);
260267
} else {
268+
assertLegacyDecodingEnabledIfRequired();
261269
renderFromSubtitles(positionUs);
262270
}
263271
}
264272

273+
/**
274+
* Sets whether to decode subtitle data during rendering.
275+
*
276+
* <p>If this is enabled, then the {@link SubtitleDecoderFactory} passed to the constructor is
277+
* used to decode subtitle data during rendering.
278+
*
279+
* <p>If this is disabled this text renderer can only handle tracks with MIME type {@link
280+
* MimeTypes#APPLICATION_MEDIA3_CUES} (which have been parsed from their original format during
281+
* extraction), and will throw an exception if passed data of a different type.
282+
*
283+
* <p>This is enabled by default.
284+
*
285+
* <p>This method is experimental. It may change behavior, be renamed, or removed in a future
286+
* release.
287+
*/
288+
public void experimentalSetLegacyDecodingEnabled(boolean legacyDecodingEnabled) {
289+
this.legacyDecodingEnabled = legacyDecodingEnabled;
290+
}
291+
265292
@RequiresNonNull("this.cuesResolver")
266293
private void renderFromCuesWithTiming(long positionUs) {
267294
boolean outputNeedsUpdating = readAndDecodeCuesWithTiming(positionUs);
@@ -558,7 +585,22 @@ private long getPresentationTimeUs(long positionUs) {
558585
return positionUs - outputStreamOffsetUs;
559586
}
560587

588+
@RequiresNonNull("streamFormat")
589+
private void assertLegacyDecodingEnabledIfRequired() {
590+
checkState(
591+
legacyDecodingEnabled
592+
|| Objects.equals(streamFormat.sampleMimeType, MimeTypes.APPLICATION_CEA608)
593+
|| Objects.equals(streamFormat.sampleMimeType, MimeTypes.APPLICATION_MP4CEA608)
594+
|| Objects.equals(streamFormat.sampleMimeType, MimeTypes.APPLICATION_CEA708),
595+
"Legacy decoding is disabled, can't handle "
596+
+ streamFormat.sampleMimeType
597+
+ " samples (expected "
598+
+ MimeTypes.APPLICATION_MEDIA3_CUES
599+
+ ").");
600+
}
601+
561602
/** Returns whether {@link Format#sampleMimeType} is {@link MimeTypes#APPLICATION_MEDIA3_CUES}. */
603+
@SideEffectFree
562604
private static boolean isCuesWithTiming(Format format) {
563605
return Objects.equals(format.sampleMimeType, MimeTypes.APPLICATION_MEDIA3_CUES);
564606
}

libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/WebvttPlaybackTest.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,26 @@
1515
*/
1616
package androidx.media3.exoplayer.e2etest;
1717

18+
import static com.google.common.truth.Truth.assertThat;
19+
1820
import android.content.Context;
1921
import android.graphics.SurfaceTexture;
2022
import android.net.Uri;
23+
import android.os.Looper;
2124
import android.view.Surface;
2225
import androidx.media3.common.C;
2326
import androidx.media3.common.MediaItem;
2427
import androidx.media3.common.MimeTypes;
2528
import androidx.media3.common.Player;
29+
import androidx.media3.exoplayer.DefaultRenderersFactory;
30+
import androidx.media3.exoplayer.ExoPlaybackException;
2631
import androidx.media3.exoplayer.ExoPlayer;
32+
import androidx.media3.exoplayer.Renderer;
33+
import androidx.media3.exoplayer.RenderersFactory;
2734
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
2835
import androidx.media3.exoplayer.source.MediaSource;
36+
import androidx.media3.exoplayer.text.TextOutput;
37+
import androidx.media3.exoplayer.text.TextRenderer;
2938
import androidx.media3.test.utils.CapturingRenderersFactory;
3039
import androidx.media3.test.utils.DumpFileAsserts;
3140
import androidx.media3.test.utils.FakeClock;
@@ -34,6 +43,8 @@
3443
import androidx.media3.test.utils.robolectric.TestPlayerRunHelper;
3544
import androidx.test.core.app.ApplicationProvider;
3645
import com.google.common.collect.ImmutableList;
46+
import com.google.common.collect.Iterables;
47+
import java.util.ArrayList;
3748
import org.junit.Rule;
3849
import org.junit.Test;
3950
import org.junit.runner.RunWith;
@@ -92,4 +103,55 @@ public void test() throws Exception {
92103
DumpFileAsserts.assertOutput(
93104
applicationContext, playbackOutput, "playbackdumps/webvtt/" + inputFile + ".dump");
94105
}
106+
107+
@Test
108+
public void textRendererDoesntSupportLegacyDecoding_playbackFails() throws Exception {
109+
Context applicationContext = ApplicationProvider.getApplicationContext();
110+
RenderersFactory renderersFactory =
111+
new DefaultRenderersFactory(applicationContext) {
112+
@Override
113+
protected void buildTextRenderers(
114+
Context context,
115+
TextOutput output,
116+
Looper outputLooper,
117+
@ExtensionRendererMode int extensionRendererMode,
118+
ArrayList<Renderer> out) {
119+
super.buildTextRenderers(context, output, outputLooper, extensionRendererMode, out);
120+
((TextRenderer) Iterables.getLast(out)).experimentalSetLegacyDecodingEnabled(false);
121+
}
122+
};
123+
MediaSource.Factory mediaSourceFactory =
124+
new DefaultMediaSourceFactory(applicationContext)
125+
.experimentalParseSubtitlesDuringExtraction(false);
126+
ExoPlayer player =
127+
new ExoPlayer.Builder(applicationContext, renderersFactory)
128+
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
129+
.setMediaSourceFactory(mediaSourceFactory)
130+
.build();
131+
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
132+
player.setVideoSurface(surface);
133+
MediaItem mediaItem =
134+
new MediaItem.Builder()
135+
.setUri("asset:///media/mp4/preroll-5s.mp4")
136+
.setSubtitleConfigurations(
137+
ImmutableList.of(
138+
new MediaItem.SubtitleConfiguration.Builder(
139+
Uri.parse("asset:///media/webvtt/" + inputFile))
140+
.setMimeType(MimeTypes.TEXT_VTT)
141+
.setLanguage("en")
142+
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
143+
.build()))
144+
.build();
145+
146+
player.setMediaItem(mediaItem);
147+
player.prepare();
148+
player.play();
149+
ExoPlaybackException playbackException = TestPlayerRunHelper.runUntilError(player);
150+
assertThat(playbackException)
151+
.hasCauseThat()
152+
.hasMessageThat()
153+
.contains("Legacy decoding is disabled");
154+
player.release();
155+
surface.release();
156+
}
95157
}

0 commit comments

Comments
 (0)