diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlayerTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlayerTest.java index 5143e0cea2..2e8ade80aa 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlayerTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlayerTest.java @@ -20,10 +20,14 @@ import static androidx.media3.transformer.AndroidTestUtil.JPG_SINGLE_PIXEL_ASSET import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET; import static com.google.common.truth.Truth.assertThat; import static com.google.common.util.concurrent.Futures.immediateFuture; +import static org.junit.Assert.assertThrows; import android.app.Instrumentation; import android.content.Context; import android.graphics.BitmapFactory; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; import android.util.Pair; import android.view.SurfaceHolder; import android.view.SurfaceView; @@ -33,8 +37,11 @@ import androidx.media3.common.ColorInfo; import androidx.media3.common.DebugViewProvider; import androidx.media3.common.Effect; import androidx.media3.common.Format; +import androidx.media3.common.GlObjectsProvider; +import androidx.media3.common.GlTextureInfo; import androidx.media3.common.MediaItem; import androidx.media3.common.MimeTypes; +import androidx.media3.common.PlaybackException; import androidx.media3.common.VideoCompositorSettings; import androidx.media3.common.VideoGraph; import androidx.media3.common.audio.AudioProcessor; @@ -449,6 +456,66 @@ public class CompositionPlayerTest { listener.waitUntilPlayerEnded(); } + @Test + public void setGlObjectsProvider_withFailingImplementation_throws() { + PlayerTestListener listener = new PlayerTestListener(TEST_TIMEOUT_MS); + EditedMediaItem video = + new EditedMediaItem.Builder(MediaItem.fromUri(MP4_ASSET.uri)) + .setDurationUs(MP4_ASSET.videoDurationUs) + .build(); + + instrumentation.runOnMainSync( + () -> { + compositionPlayer = + new CompositionPlayer.Builder(applicationContext) + .setGlObjectsProvider( + new GlObjectsProvider() { + @Override + public EGLContext createEglContext( + EGLDisplay eglDisplay, int openGlVersion, int[] configAttributes) { + throw new UnsupportedOperationException(); + } + + @Override + public EGLSurface createEglSurface( + EGLDisplay eglDisplay, + Object surface, + @C.ColorTransfer int colorTransfer, + boolean isEncoderInputSurface) { + throw new UnsupportedOperationException(); + } + + @Override + public EGLSurface createFocusedPlaceholderEglSurface( + EGLContext eglContext, EGLDisplay eglDisplay) { + throw new UnsupportedOperationException(); + } + + @Override + public GlTextureInfo createBuffersForTexture( + int texId, int width, int height) { + throw new UnsupportedOperationException(); + } + + @Override + public void release(EGLDisplay eglDisplay) { + throw new UnsupportedOperationException(); + } + }) + .build(); + // Set a surface on the player even though there is no UI on this test. We need a surface + // otherwise the player will skip/drop video frames. + compositionPlayer.setVideoSurfaceView(surfaceView); + compositionPlayer.addListener(listener); + compositionPlayer.setComposition( + new Composition.Builder(new EditedMediaItemSequence.Builder(video).build()).build()); + compositionPlayer.prepare(); + compositionPlayer.play(); + }); + + assertThrows(PlaybackException.class, listener::waitUntilPlayerEnded); + } + @Test public void release_videoGraphWrapperFailsDuringRelease_playerDoesNotRaiseError() throws Exception { 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 642cd8438f..eb03e3a42d 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayer.java @@ -38,6 +38,7 @@ import androidx.annotation.RestrictTo; import androidx.annotation.VisibleForTesting; import androidx.media3.common.C; import androidx.media3.common.Effect; +import androidx.media3.common.GlObjectsProvider; import androidx.media3.common.MediaItem; import androidx.media3.common.PlaybackException; import androidx.media3.common.Player; @@ -53,6 +54,7 @@ import androidx.media3.common.util.Log; import androidx.media3.common.util.Size; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; +import androidx.media3.effect.DefaultGlObjectsProvider; import androidx.media3.effect.DefaultVideoFrameProcessor; import androidx.media3.effect.SingleInputVideoGraph; import androidx.media3.effect.TimestampAdjustment; @@ -128,6 +130,8 @@ public final class CompositionPlayer extends SimpleBasePlayer private boolean videoPrewarmingEnabled; private Clock clock; private VideoGraph.@MonotonicNonNull Factory videoGraphFactory; + + private @MonotonicNonNull GlObjectsProvider glObjectsProvider; private boolean enableReplayableCache; private boolean built; @@ -250,6 +254,22 @@ public final class CompositionPlayer extends SimpleBasePlayer return this; } + /** + * Sets the {@link GlObjectsProvider} to be used by the effect processing pipeline. + * + *

Setting a {@link GlObjectsProvider} is no-op if a {@link VideoGraph.Factory} is + * {@linkplain #setVideoGraphFactory set}. By default, a {@link DefaultGlObjectsProvider} is + * used. + * + * @param glObjectsProvider The {@link GlObjectsProvider}. + * @return This builder, for convenience. + */ + @CanIgnoreReturnValue + public Builder setGlObjectsProvider(GlObjectsProvider glObjectsProvider) { + this.glObjectsProvider = glObjectsProvider; + return this; + } + /** * Sets whether to enable replayable cache. * @@ -281,11 +301,14 @@ public final class CompositionPlayer extends SimpleBasePlayer audioSink = new DefaultAudioSink.Builder(context).build(); } if (videoGraphFactory == null) { + DefaultVideoFrameProcessor.Factory.Builder videoFrameProcessorFactoryBuilder = + new DefaultVideoFrameProcessor.Factory.Builder() + .setEnableReplayableCache(enableReplayableCache); + if (glObjectsProvider != null) { + videoFrameProcessorFactoryBuilder.setGlObjectsProvider(glObjectsProvider); + } videoGraphFactory = - new SingleInputVideoGraph.Factory( - new DefaultVideoFrameProcessor.Factory.Builder() - .setEnableReplayableCache(enableReplayableCache) - .build()); + new SingleInputVideoGraph.Factory(videoFrameProcessorFactoryBuilder.build()); } CompositionPlayer compositionPlayer = new CompositionPlayer(this); AnalyticsCollector analyticsCollector = new DefaultAnalyticsCollector(clock);