mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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
This commit is contained in:
parent
2c8ba50524
commit
80bfa819c0
@ -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.
|
||||
*
|
||||
* <p>If this is enabled, then the {@link SubtitleDecoderFactory} passed to the constructor is
|
||||
* used to decode subtitle data during rendering.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>This is enabled by default.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
|
@ -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<Renderer> out) {
|
||||
super.buildTextRenderers(context, output, outputLooper, extensionRendererMode, out);
|
||||
((TextRenderer) Iterables.getLast(out)).experimentalSetLegacyDecodingEnabled(false);
|
||||
}
|
||||
};
|
||||
MediaSource.Factory mediaSourceFactory =
|
||||
new DefaultMediaSourceFactory(applicationContext)
|
||||
.experimentalParseSubtitlesDuringExtraction(false);
|
||||
ExoPlayer player =
|
||||
new ExoPlayer.Builder(applicationContext, renderersFactory)
|
||||
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.build();
|
||||
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
|
||||
player.setVideoSurface(surface);
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setUri("asset:///media/mp4/preroll-5s.mp4")
|
||||
.setSubtitleConfigurations(
|
||||
ImmutableList.of(
|
||||
new MediaItem.SubtitleConfiguration.Builder(
|
||||
Uri.parse("asset:///media/webvtt/" + inputFile))
|
||||
.setMimeType(MimeTypes.TEXT_VTT)
|
||||
.setLanguage("en")
|
||||
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
player.setMediaItem(mediaItem);
|
||||
player.prepare();
|
||||
player.play();
|
||||
ExoPlaybackException playbackException = TestPlayerRunHelper.runUntilError(player);
|
||||
assertThat(playbackException)
|
||||
.hasCauseThat()
|
||||
.hasMessageThat()
|
||||
.contains("Legacy decoding is disabled");
|
||||
player.release();
|
||||
surface.release();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user