From 304c4e41f872ba225b6d9f4f930499c1c96ce9dc Mon Sep 17 00:00:00 2001 From: kimvde Date: Tue, 25 Jun 2024 01:47:49 -0700 Subject: [PATCH] Call VideoFrameReleaseControl.isReady from VideoSink when enabled PiperOrigin-RevId: 646385384 --- .../video/CompositingVideoSinkProvider.java | 10 +++++---- .../video/MediaCodecVideoRenderer.java | 9 +++++--- .../video/VideoFrameReleaseControl.java | 10 +++++---- .../video/VideoFrameRenderControl.java | 4 ++-- .../media3/exoplayer/video/VideoSink.java | 8 ++++++- .../video/VideoFrameReleaseControlTest.java | 22 +++++++++---------- .../video/VideoFrameRenderControlTest.java | 8 +++---- .../SequencePlayerRenderersWrapper.java | 18 +++++++-------- 8 files changed, 51 insertions(+), 38 deletions(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/CompositingVideoSinkProvider.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/CompositingVideoSinkProvider.java index 95e0558ae6..65081b0dfd 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/CompositingVideoSinkProvider.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/CompositingVideoSinkProvider.java @@ -426,8 +426,9 @@ public final class CompositingVideoSinkProvider implements VideoSinkProvider, Vi } } - private boolean isReady() { - return pendingFlushCount == 0 && videoFrameRenderControl.isReady(); + private boolean isReady(boolean rendererOtherwiseReady) { + return videoFrameRenderControl.isReady( + /* rendererOtherwiseReady= */ rendererOtherwiseReady && pendingFlushCount == 0); } private boolean hasReleasedFrame(long presentationTimeUs) { @@ -585,8 +586,9 @@ public final class CompositingVideoSinkProvider implements VideoSinkProvider, Vi } @Override - public boolean isReady() { - return isInitialized() && CompositingVideoSinkProvider.this.isReady(); + public boolean isReady(boolean rendererOtherwiseReady) { + return CompositingVideoSinkProvider.this.isReady( + /* rendererOtherwiseReady= */ rendererOtherwiseReady && isInitialized()); } @Override diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java index 8a90f05e2f..af5a7002da 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/MediaCodecVideoRenderer.java @@ -748,15 +748,18 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer @Override public boolean isReady() { - boolean readyToReleaseFrames = super.isReady() && (videoSink == null || videoSink.isReady()); - if (readyToReleaseFrames + boolean rendererOtherwiseReady = super.isReady(); + if (videoSink != null) { + return videoSink.isReady(rendererOtherwiseReady); + } + if (rendererOtherwiseReady && ((placeholderSurface != null && displaySurface == placeholderSurface) || getCodec() == null || tunneling)) { // Not releasing frames. return true; } - return videoFrameReleaseControl.isReady(readyToReleaseFrames); + return videoFrameReleaseControl.isReady(rendererOtherwiseReady); } @Override diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoFrameReleaseControl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoFrameReleaseControl.java index 565322d994..1ffa491e4e 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoFrameReleaseControl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoFrameReleaseControl.java @@ -277,12 +277,14 @@ public final class VideoFrameReleaseControl { /** * Whether the release control is ready to start playback. * - * @see Renderer#isReady() - * @param rendererReady Whether the renderer is ready. + *

The renderer should be {@linkplain Renderer#isReady() ready} if and only if the release + * control is ready. + * + * @param rendererOtherwiseReady Whether the renderer is ready except for the release control. * @return Whether the release control is ready. */ - public boolean isReady(boolean rendererReady) { - if (rendererReady && firstFrameState == C.FIRST_FRAME_RENDERED) { + public boolean isReady(boolean rendererOtherwiseReady) { + if (rendererOtherwiseReady && firstFrameState == C.FIRST_FRAME_RENDERED) { // Ready. If we were joining then we've now joined, so clear the joining deadline. joiningDeadlineMs = C.TIME_UNSET; return true; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoFrameRenderControl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoFrameRenderControl.java index 624c977c81..ed8e089daf 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoFrameRenderControl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoFrameRenderControl.java @@ -123,8 +123,8 @@ import androidx.media3.exoplayer.ExoPlaybackException; } /** Returns whether the renderer is ready. */ - public boolean isReady() { - return videoFrameReleaseControl.isReady(/* rendererReady= */ true); + public boolean isReady(boolean rendererOtherwiseReady) { + return videoFrameReleaseControl.isReady(rendererOtherwiseReady); } /** diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoSink.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoSink.java index a360f5de67..091281fe94 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoSink.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoSink.java @@ -28,6 +28,7 @@ import androidx.media3.common.VideoSize; import androidx.media3.common.util.Size; import androidx.media3.common.util.TimestampIterator; import androidx.media3.common.util.UnstableApi; +import androidx.media3.exoplayer.Renderer; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -148,8 +149,13 @@ public interface VideoSink { /** * Returns whether the video sink is able to immediately render media from the current position. + * + *

The renderer should be {@linkplain Renderer#isReady() ready} if and only if the video sink + * is ready. + * + * @param rendererOtherwiseReady Whether the renderer is ready except for the video sink. */ - boolean isReady(); + boolean isReady(boolean rendererOtherwiseReady); /** * Returns whether all queued video frames have been rendered, including the frame marked as last diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/VideoFrameReleaseControlTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/VideoFrameReleaseControlTest.java index 109efcf04a..6e6f8cfc86 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/VideoFrameReleaseControlTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/VideoFrameReleaseControlTest.java @@ -31,8 +31,8 @@ public class VideoFrameReleaseControlTest { public void isReady_onNewInstance_returnsFalse() { VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl(); - assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ true)).isFalse(); - assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ false)).isFalse(); + assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ true)).isFalse(); + assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ false)).isFalse(); } @Test @@ -40,7 +40,7 @@ public class VideoFrameReleaseControlTest { VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl(); assertThat(videoFrameReleaseControl.onFrameReleasedIsFirstFrame()).isTrue(); - assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ true)).isTrue(); + assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ true)).isTrue(); } @Test @@ -52,7 +52,7 @@ public class VideoFrameReleaseControlTest { videoFrameReleaseControl.join(/* renderNextFrameImmediately= */ true); - assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ false)).isTrue(); + assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ false)).isTrue(); } @Test @@ -64,7 +64,7 @@ public class VideoFrameReleaseControlTest { videoFrameReleaseControl.join(/* renderNextFrameImmediately= */ false); - assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ false)).isTrue(); + assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ false)).isTrue(); } @Test @@ -75,11 +75,11 @@ public class VideoFrameReleaseControlTest { videoFrameReleaseControl.setClock(clock); videoFrameReleaseControl.join(/* renderNextFrameImmediately= */ true); - assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ false)).isTrue(); + assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ false)).isTrue(); clock.advanceTime(/* timeDiffMs= */ 101); - assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ false)).isFalse(); + assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ false)).isFalse(); } @Test @@ -90,11 +90,11 @@ public class VideoFrameReleaseControlTest { videoFrameReleaseControl.setClock(clock); videoFrameReleaseControl.join(/* renderNextFrameImmediately= */ false); - assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ false)).isTrue(); + assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ false)).isTrue(); clock.advanceTime(/* timeDiffMs= */ 101); - assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ false)).isFalse(); + assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ false)).isFalse(); } @Test @@ -132,10 +132,10 @@ public class VideoFrameReleaseControlTest { VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl(); videoFrameReleaseControl.onFrameReleasedIsFirstFrame(); - assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ true)).isTrue(); + assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ true)).isTrue(); videoFrameReleaseControl.reset(); - assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ true)).isFalse(); + assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ true)).isFalse(); } @Test diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/VideoFrameRenderControlTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/VideoFrameRenderControlTest.java index 92dda8db51..727f4dd57c 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/VideoFrameRenderControlTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/video/VideoFrameRenderControlTest.java @@ -43,7 +43,7 @@ public class VideoFrameRenderControlTest { new VideoFrameRenderControl( mock(VideoFrameRenderControl.FrameRenderer.class), createVideoFrameReleaseControl()); - assertThat(videoFrameRenderControl.isReady()).isFalse(); + assertThat(videoFrameRenderControl.isReady(/* rendererOtherwiseReady= */ true)).isFalse(); } @Test @@ -60,7 +60,7 @@ public class VideoFrameRenderControlTest { videoFrameRenderControl.onOutputFrameAvailableForRendering(/* presentationTimeUs= */ 0); videoFrameRenderControl.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); - assertThat(videoFrameRenderControl.isReady()).isTrue(); + assertThat(videoFrameRenderControl.isReady(/* rendererOtherwiseReady= */ true)).isTrue(); InOrder inOrder = Mockito.inOrder(frameRenderer); inOrder .verify(frameRenderer) @@ -92,7 +92,7 @@ public class VideoFrameRenderControlTest { videoFrameRenderControl.onOutputFrameAvailableForRendering(/* presentationTimeUs= */ 10_000); videoFrameRenderControl.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); - assertThat(videoFrameRenderControl.isReady()).isTrue(); + assertThat(videoFrameRenderControl.isReady(/* rendererOtherwiseReady= */ true)).isTrue(); InOrder inOrder = Mockito.inOrder(frameRenderer); inOrder .verify(frameRenderer) @@ -141,7 +141,7 @@ public class VideoFrameRenderControlTest { videoFrameRenderControl.onOutputFrameAvailableForRendering(/* presentationTimeUs= */ 0); videoFrameRenderControl.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); - assertThat(videoFrameRenderControl.isReady()).isTrue(); + assertThat(videoFrameRenderControl.isReady(/* rendererOtherwiseReady= */ true)).isTrue(); InOrder inOrder = Mockito.inOrder(frameRenderer); inOrder .verify(frameRenderer) diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/SequencePlayerRenderersWrapper.java b/libraries/transformer/src/main/java/androidx/media3/transformer/SequencePlayerRenderersWrapper.java index 862766a747..bc2f65a28a 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/SequencePlayerRenderersWrapper.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/SequencePlayerRenderersWrapper.java @@ -48,7 +48,6 @@ import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.text.TextOutput; import androidx.media3.exoplayer.video.CompositingVideoSinkProvider; import androidx.media3.exoplayer.video.MediaCodecVideoRenderer; -import androidx.media3.exoplayer.video.VideoFrameReleaseControl; import androidx.media3.exoplayer.video.VideoRendererEventListener; import androidx.media3.exoplayer.video.VideoSink; import com.google.common.collect.ImmutableList; @@ -296,7 +295,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final SequencePlayerRenderersWrapper sequencePlayerRenderersWrapper; private final CompositingVideoSinkProvider compositingVideoSinkProvider; private final VideoSink videoSink; - private final VideoFrameReleaseControl videoFrameReleaseControl; private ImmutableList videoEffects; private @MonotonicNonNull ConstantRateTimestampIterator timestampIterator; @@ -314,8 +312,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; compositingVideoSinkProvider = checkStateNotNull(sequencePlayerRenderersWrapper.compositingVideoSinkProvider); videoSink = compositingVideoSinkProvider.getSink(); - videoFrameReleaseControl = - checkStateNotNull(compositingVideoSinkProvider.getVideoFrameReleaseControl()); videoEffects = ImmutableList.of(); streamOffsetUs = C.TIME_UNSET; } @@ -357,11 +353,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public boolean isReady() { - // If the renderer was enabled with mayRenderStartOfStream set to false, meaning the image - // renderer is playing after a video, we don't need to wait until the first frame is rendered. - // If the renderer was enabled with mayRenderStartOfStream, we must wait until the first frame - // is rendered, which is checked by VideoSink.isReady(). - return super.isReady() && (!mayRenderStartOfStream || videoSink.isReady()); + if (mayRenderStartOfStream) { + // The image renderer is not playing after a video. We must wait until the first frame is + // rendered. + return videoSink.isReady(/* rendererOtherwiseReady= */ super.isReady()); + } else { + // The image renderer is playing after a video. We don't need to wait until the first frame + // is rendered. + return super.isReady(); + } } @Override