diff --git a/libraries/effect/src/main/java/androidx/media3/effect/FinalShaderProgramWrapper.java b/libraries/effect/src/main/java/androidx/media3/effect/FinalShaderProgramWrapper.java index 32bfe03016..867d42abab 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/FinalShaderProgramWrapper.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/FinalShaderProgramWrapper.java @@ -327,7 +327,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } /** See {@link DefaultVideoFrameProcessor#setOutputSurfaceInfo} */ - public synchronized void setOutputSurfaceInfo(@Nullable SurfaceInfo outputSurfaceInfo) { + public void setOutputSurfaceInfo(@Nullable SurfaceInfo outputSurfaceInfo) { + try { + videoFrameProcessingTaskExecutor.invoke( + () -> setOutputSurfaceInfoInternal(outputSurfaceInfo)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + videoFrameProcessorListenerExecutor.execute( + () -> videoFrameProcessorListener.onError(VideoFrameProcessingException.from(e))); + } + } + + /** Must be called on the GL thread. */ + private synchronized void setOutputSurfaceInfoInternal(@Nullable SurfaceInfo outputSurfaceInfo) { if (textureOutputListener != null) { return; } diff --git a/libraries/effect/src/main/java/androidx/media3/effect/VideoFrameProcessingTaskExecutor.java b/libraries/effect/src/main/java/androidx/media3/effect/VideoFrameProcessingTaskExecutor.java index db675ad772..0445113edc 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/VideoFrameProcessingTaskExecutor.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/VideoFrameProcessingTaskExecutor.java @@ -24,9 +24,11 @@ import androidx.media3.common.util.GlUtil; import java.util.ArrayDeque; import java.util.Queue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeoutException; /** * Wrapper around a single thread {@link ExecutorService} for executing {@link Task} instances. @@ -61,10 +63,11 @@ import java.util.concurrent.RejectedExecutionException; void onError(VideoFrameProcessingException exception); } - private static final long RELEASE_WAIT_TIME_MS = 500; + private static final long EXECUTOR_SERVICE_TIMEOUT_MS = 500; private final boolean shouldShutdownExecutorService; private final ExecutorService singleThreadExecutorService; + private final Future threadFuture; // Used to identify the GL thread. private final ErrorListener errorListener; private final Object lock; @@ -80,6 +83,7 @@ import java.util.concurrent.RejectedExecutionException; boolean shouldShutdownExecutorService, ErrorListener errorListener) { this.singleThreadExecutorService = singleThreadExecutorService; + threadFuture = singleThreadExecutorService.submit(Thread::currentThread); this.shouldShutdownExecutorService = shouldShutdownExecutorService; this.errorListener = errorListener; lock = new Object(); @@ -106,6 +110,45 @@ import java.util.concurrent.RejectedExecutionException; } } + /** Blocks the caller until the given {@link Task} has completed. */ + public void invoke(Task task) throws InterruptedException { + // If running on the executor service thread, run synchronously. + // Calling future.get() on the single executor thread would deadlock. + Thread videoFrameProcessingThread; + try { + videoFrameProcessingThread = threadFuture.get(EXECUTOR_SERVICE_TIMEOUT_MS, MILLISECONDS); + } catch (InterruptedException e) { + throw e; + } catch (Exception e) { + handleException(e); + return; + } + if (Thread.currentThread() == videoFrameProcessingThread) { + try { + task.run(); + } catch (Exception e) { + handleException(e); + } + return; + } + + // Not running on the executor service thread. Block until task.run() returns. + try { + Future taskFuture = + singleThreadExecutorService.submit( + () -> { + try { + task.run(); + } catch (Exception e) { + handleException(e); + } + }); + taskFuture.get(EXECUTOR_SERVICE_TIMEOUT_MS, MILLISECONDS); + } catch (RuntimeException | ExecutionException | TimeoutException e) { + handleException(e); + } + } + /** * Submits the given {@link Task} to be executed after the currently running task and all * previously submitted high-priority tasks have completed. @@ -173,7 +216,8 @@ import java.util.concurrent.RejectedExecutionException; wrapTaskAndSubmitToExecutorService(releaseTask, /* isFlushOrReleaseTask= */ true); if (shouldShutdownExecutorService) { singleThreadExecutorService.shutdown(); - if (!singleThreadExecutorService.awaitTermination(RELEASE_WAIT_TIME_MS, MILLISECONDS)) { + if (!singleThreadExecutorService.awaitTermination( + EXECUTOR_SERVICE_TIMEOUT_MS, MILLISECONDS)) { errorListener.onError( new VideoFrameProcessingException( "Release timed out. OpenGL resources may not be cleaned up properly."));