diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/PlaybackVideoGraphWrapper.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/PlaybackVideoGraphWrapper.java index 5f790ec70a..a461a02332 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/PlaybackVideoGraphWrapper.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/PlaybackVideoGraphWrapper.java @@ -121,6 +121,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video private VideoFrameProcessor.@MonotonicNonNull Factory videoFrameProcessorFactory; private PreviewingVideoGraph.@MonotonicNonNull Factory previewingVideoGraphFactory; + private List compositionEffects; private Clock clock; private boolean built; @@ -128,6 +129,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video public Builder(Context context, VideoFrameReleaseControl videoFrameReleaseControl) { this.context = context.getApplicationContext(); this.videoFrameReleaseControl = videoFrameReleaseControl; + compositionEffects = ImmutableList.of(); clock = Clock.DEFAULT; } @@ -164,6 +166,18 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video return this; } + /** + * Sets the {@linkplain Effect effects} to apply after compositing the sinks' data. + * + * @param compositionEffects The composition {@linkplain Effect effects}. + * @return This builder, for convenience. + */ + @CanIgnoreReturnValue + public Builder setCompositionEffects(List compositionEffects) { + this.compositionEffects = compositionEffects; + return this; + } + /** * Sets the {@link Clock} that will be used. * @@ -217,6 +231,8 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video private final VideoFrameReleaseControl videoFrameReleaseControl; private final VideoFrameRenderControl videoFrameRenderControl; private final PreviewingVideoGraph.Factory previewingVideoGraphFactory; + + private final List compositionEffects; private final Clock clock; private final CopyOnWriteArraySet listeners; @@ -244,6 +260,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video videoFrameRenderControl = new VideoFrameRenderControl(new FrameRendererImpl(), videoFrameReleaseControl); previewingVideoGraphFactory = checkStateNotNull(builder.previewingVideoGraphFactory); + compositionEffects = builder.compositionEffects; listeners = new CopyOnWriteArraySet<>(); state = STATE_CREATED; addListener(videoSinkImpl); @@ -642,6 +659,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video public void setPendingVideoEffects(List videoEffects) { this.videoEffects.clear(); this.videoEffects.addAll(videoEffects); + this.videoEffects.addAll(compositionEffects); } @Override diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlaybackTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlaybackTest.java index a0d5ce2c3d..e7099c906e 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlaybackTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlaybackTest.java @@ -376,4 +376,42 @@ public class CompositionPlaybackTest { assertThat(inputTimestampRecordingShaderProgram.getInputTimestampsUs()).isEmpty(); } + + @Test + public void playback_withCompositionEffect_effectIsApplied() throws Exception { + EditedMediaItem editedMediaItem = + new EditedMediaItem.Builder(VIDEO_MEDIA_ITEM).setDurationUs(VIDEO_DURATION_US).build(); + InputTimestampRecordingShaderProgram inputTimestampRecordingShaderProgram = + new InputTimestampRecordingShaderProgram(); + Effect videoEffect = (GlEffect) (context, useHdr) -> inputTimestampRecordingShaderProgram; + Composition composition = + new Composition.Builder( + new EditedMediaItemSequence.Builder(editedMediaItem, editedMediaItem).build()) + .setEffects( + new Effects( + /* audioProcessors= */ ImmutableList.of(), + /* videoEffects= */ ImmutableList.of(videoEffect))) + .build(); + ImmutableList expectedTimestampsUs = + new ImmutableList.Builder() + .addAll(VIDEO_TIMESTAMPS_US) + .addAll( + Iterables.transform( + VIDEO_TIMESTAMPS_US, timestampUs -> (VIDEO_DURATION_US + timestampUs))) + .build(); + + getInstrumentation() + .runOnMainSync( + () -> { + player = new CompositionPlayer.Builder(context).build(); + player.addListener(playerTestListener); + player.setComposition(composition); + player.prepare(); + player.play(); + }); + playerTestListener.waitUntilPlayerEnded(); + + assertThat(inputTimestampRecordingShaderProgram.getInputTimestampsUs()) + .isEqualTo(expectedTimestampsUs); + } } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayer.java index 53a20ca247..50a2a30660 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayer.java @@ -673,6 +673,7 @@ public final class CompositionPlayer extends SimpleBasePlayer PlaybackVideoGraphWrapper playbackVideoGraphWrapper = new PlaybackVideoGraphWrapper.Builder(context, videoFrameReleaseControl) .setPreviewingVideoGraphFactory(checkNotNull(previewingVideoGraphFactory)) + .setCompositionEffects(composition.effects.videoEffects) .setClock(clock) .build(); playbackVideoGraphWrapper.addListener(this);