Call VideoFrameReleaseControl.isReady from VideoSink when enabled

PiperOrigin-RevId: 646385384
This commit is contained in:
kimvde 2024-06-25 01:47:49 -07:00 committed by Copybara-Service
parent babc9c69c6
commit 304c4e41f8
8 changed files with 51 additions and 38 deletions

View File

@ -426,8 +426,9 @@ public final class CompositingVideoSinkProvider implements VideoSinkProvider, Vi
} }
} }
private boolean isReady() { private boolean isReady(boolean rendererOtherwiseReady) {
return pendingFlushCount == 0 && videoFrameRenderControl.isReady(); return videoFrameRenderControl.isReady(
/* rendererOtherwiseReady= */ rendererOtherwiseReady && pendingFlushCount == 0);
} }
private boolean hasReleasedFrame(long presentationTimeUs) { private boolean hasReleasedFrame(long presentationTimeUs) {
@ -585,8 +586,9 @@ public final class CompositingVideoSinkProvider implements VideoSinkProvider, Vi
} }
@Override @Override
public boolean isReady() { public boolean isReady(boolean rendererOtherwiseReady) {
return isInitialized() && CompositingVideoSinkProvider.this.isReady(); return CompositingVideoSinkProvider.this.isReady(
/* rendererOtherwiseReady= */ rendererOtherwiseReady && isInitialized());
} }
@Override @Override

View File

@ -748,15 +748,18 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
@Override @Override
public boolean isReady() { public boolean isReady() {
boolean readyToReleaseFrames = super.isReady() && (videoSink == null || videoSink.isReady()); boolean rendererOtherwiseReady = super.isReady();
if (readyToReleaseFrames if (videoSink != null) {
return videoSink.isReady(rendererOtherwiseReady);
}
if (rendererOtherwiseReady
&& ((placeholderSurface != null && displaySurface == placeholderSurface) && ((placeholderSurface != null && displaySurface == placeholderSurface)
|| getCodec() == null || getCodec() == null
|| tunneling)) { || tunneling)) {
// Not releasing frames. // Not releasing frames.
return true; return true;
} }
return videoFrameReleaseControl.isReady(readyToReleaseFrames); return videoFrameReleaseControl.isReady(rendererOtherwiseReady);
} }
@Override @Override

View File

@ -277,12 +277,14 @@ public final class VideoFrameReleaseControl {
/** /**
* Whether the release control is ready to start playback. * Whether the release control is ready to start playback.
* *
* @see Renderer#isReady() * <p>The renderer should be {@linkplain Renderer#isReady() ready} if and only if the release
* @param rendererReady Whether the renderer is ready. * control is ready.
*
* @param rendererOtherwiseReady Whether the renderer is ready except for the release control.
* @return Whether the release control is ready. * @return Whether the release control is ready.
*/ */
public boolean isReady(boolean rendererReady) { public boolean isReady(boolean rendererOtherwiseReady) {
if (rendererReady && firstFrameState == C.FIRST_FRAME_RENDERED) { if (rendererOtherwiseReady && firstFrameState == C.FIRST_FRAME_RENDERED) {
// Ready. If we were joining then we've now joined, so clear the joining deadline. // Ready. If we were joining then we've now joined, so clear the joining deadline.
joiningDeadlineMs = C.TIME_UNSET; joiningDeadlineMs = C.TIME_UNSET;
return true; return true;

View File

@ -123,8 +123,8 @@ import androidx.media3.exoplayer.ExoPlaybackException;
} }
/** Returns whether the renderer is ready. */ /** Returns whether the renderer is ready. */
public boolean isReady() { public boolean isReady(boolean rendererOtherwiseReady) {
return videoFrameReleaseControl.isReady(/* rendererReady= */ true); return videoFrameReleaseControl.isReady(rendererOtherwiseReady);
} }
/** /**

View File

@ -28,6 +28,7 @@ import androidx.media3.common.VideoSize;
import androidx.media3.common.util.Size; import androidx.media3.common.util.Size;
import androidx.media3.common.util.TimestampIterator; import androidx.media3.common.util.TimestampIterator;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.Renderer;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; 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. * Returns whether the video sink is able to immediately render media from the current position.
*
* <p>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 * Returns whether all queued video frames have been rendered, including the frame marked as last

View File

@ -31,8 +31,8 @@ public class VideoFrameReleaseControlTest {
public void isReady_onNewInstance_returnsFalse() { public void isReady_onNewInstance_returnsFalse() {
VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl(); VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl();
assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ true)).isFalse(); assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ true)).isFalse();
assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ false)).isFalse(); assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ false)).isFalse();
} }
@Test @Test
@ -40,7 +40,7 @@ public class VideoFrameReleaseControlTest {
VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl(); VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl();
assertThat(videoFrameReleaseControl.onFrameReleasedIsFirstFrame()).isTrue(); assertThat(videoFrameReleaseControl.onFrameReleasedIsFirstFrame()).isTrue();
assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ true)).isTrue(); assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ true)).isTrue();
} }
@Test @Test
@ -52,7 +52,7 @@ public class VideoFrameReleaseControlTest {
videoFrameReleaseControl.join(/* renderNextFrameImmediately= */ true); videoFrameReleaseControl.join(/* renderNextFrameImmediately= */ true);
assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ false)).isTrue(); assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ false)).isTrue();
} }
@Test @Test
@ -64,7 +64,7 @@ public class VideoFrameReleaseControlTest {
videoFrameReleaseControl.join(/* renderNextFrameImmediately= */ false); videoFrameReleaseControl.join(/* renderNextFrameImmediately= */ false);
assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ false)).isTrue(); assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ false)).isTrue();
} }
@Test @Test
@ -75,11 +75,11 @@ public class VideoFrameReleaseControlTest {
videoFrameReleaseControl.setClock(clock); videoFrameReleaseControl.setClock(clock);
videoFrameReleaseControl.join(/* renderNextFrameImmediately= */ true); videoFrameReleaseControl.join(/* renderNextFrameImmediately= */ true);
assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ false)).isTrue(); assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ false)).isTrue();
clock.advanceTime(/* timeDiffMs= */ 101); clock.advanceTime(/* timeDiffMs= */ 101);
assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ false)).isFalse(); assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ false)).isFalse();
} }
@Test @Test
@ -90,11 +90,11 @@ public class VideoFrameReleaseControlTest {
videoFrameReleaseControl.setClock(clock); videoFrameReleaseControl.setClock(clock);
videoFrameReleaseControl.join(/* renderNextFrameImmediately= */ false); videoFrameReleaseControl.join(/* renderNextFrameImmediately= */ false);
assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ false)).isTrue(); assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ false)).isTrue();
clock.advanceTime(/* timeDiffMs= */ 101); clock.advanceTime(/* timeDiffMs= */ 101);
assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ false)).isFalse(); assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ false)).isFalse();
} }
@Test @Test
@ -132,10 +132,10 @@ public class VideoFrameReleaseControlTest {
VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl(); VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl();
videoFrameReleaseControl.onFrameReleasedIsFirstFrame(); videoFrameReleaseControl.onFrameReleasedIsFirstFrame();
assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ true)).isTrue(); assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ true)).isTrue();
videoFrameReleaseControl.reset(); videoFrameReleaseControl.reset();
assertThat(videoFrameReleaseControl.isReady(/* rendererReady= */ true)).isFalse(); assertThat(videoFrameReleaseControl.isReady(/* rendererOtherwiseReady= */ true)).isFalse();
} }
@Test @Test

View File

@ -43,7 +43,7 @@ public class VideoFrameRenderControlTest {
new VideoFrameRenderControl( new VideoFrameRenderControl(
mock(VideoFrameRenderControl.FrameRenderer.class), createVideoFrameReleaseControl()); mock(VideoFrameRenderControl.FrameRenderer.class), createVideoFrameReleaseControl());
assertThat(videoFrameRenderControl.isReady()).isFalse(); assertThat(videoFrameRenderControl.isReady(/* rendererOtherwiseReady= */ true)).isFalse();
} }
@Test @Test
@ -60,7 +60,7 @@ public class VideoFrameRenderControlTest {
videoFrameRenderControl.onOutputFrameAvailableForRendering(/* presentationTimeUs= */ 0); videoFrameRenderControl.onOutputFrameAvailableForRendering(/* presentationTimeUs= */ 0);
videoFrameRenderControl.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); videoFrameRenderControl.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0);
assertThat(videoFrameRenderControl.isReady()).isTrue(); assertThat(videoFrameRenderControl.isReady(/* rendererOtherwiseReady= */ true)).isTrue();
InOrder inOrder = Mockito.inOrder(frameRenderer); InOrder inOrder = Mockito.inOrder(frameRenderer);
inOrder inOrder
.verify(frameRenderer) .verify(frameRenderer)
@ -92,7 +92,7 @@ public class VideoFrameRenderControlTest {
videoFrameRenderControl.onOutputFrameAvailableForRendering(/* presentationTimeUs= */ 10_000); videoFrameRenderControl.onOutputFrameAvailableForRendering(/* presentationTimeUs= */ 10_000);
videoFrameRenderControl.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); videoFrameRenderControl.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0);
assertThat(videoFrameRenderControl.isReady()).isTrue(); assertThat(videoFrameRenderControl.isReady(/* rendererOtherwiseReady= */ true)).isTrue();
InOrder inOrder = Mockito.inOrder(frameRenderer); InOrder inOrder = Mockito.inOrder(frameRenderer);
inOrder inOrder
.verify(frameRenderer) .verify(frameRenderer)
@ -141,7 +141,7 @@ public class VideoFrameRenderControlTest {
videoFrameRenderControl.onOutputFrameAvailableForRendering(/* presentationTimeUs= */ 0); videoFrameRenderControl.onOutputFrameAvailableForRendering(/* presentationTimeUs= */ 0);
videoFrameRenderControl.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); videoFrameRenderControl.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0);
assertThat(videoFrameRenderControl.isReady()).isTrue(); assertThat(videoFrameRenderControl.isReady(/* rendererOtherwiseReady= */ true)).isTrue();
InOrder inOrder = Mockito.inOrder(frameRenderer); InOrder inOrder = Mockito.inOrder(frameRenderer);
inOrder inOrder
.verify(frameRenderer) .verify(frameRenderer)

View File

@ -48,7 +48,6 @@ import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.text.TextOutput; import androidx.media3.exoplayer.text.TextOutput;
import androidx.media3.exoplayer.video.CompositingVideoSinkProvider; import androidx.media3.exoplayer.video.CompositingVideoSinkProvider;
import androidx.media3.exoplayer.video.MediaCodecVideoRenderer; import androidx.media3.exoplayer.video.MediaCodecVideoRenderer;
import androidx.media3.exoplayer.video.VideoFrameReleaseControl;
import androidx.media3.exoplayer.video.VideoRendererEventListener; import androidx.media3.exoplayer.video.VideoRendererEventListener;
import androidx.media3.exoplayer.video.VideoSink; import androidx.media3.exoplayer.video.VideoSink;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -296,7 +295,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final SequencePlayerRenderersWrapper sequencePlayerRenderersWrapper; private final SequencePlayerRenderersWrapper sequencePlayerRenderersWrapper;
private final CompositingVideoSinkProvider compositingVideoSinkProvider; private final CompositingVideoSinkProvider compositingVideoSinkProvider;
private final VideoSink videoSink; private final VideoSink videoSink;
private final VideoFrameReleaseControl videoFrameReleaseControl;
private ImmutableList<Effect> videoEffects; private ImmutableList<Effect> videoEffects;
private @MonotonicNonNull ConstantRateTimestampIterator timestampIterator; private @MonotonicNonNull ConstantRateTimestampIterator timestampIterator;
@ -314,8 +312,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
compositingVideoSinkProvider = compositingVideoSinkProvider =
checkStateNotNull(sequencePlayerRenderersWrapper.compositingVideoSinkProvider); checkStateNotNull(sequencePlayerRenderersWrapper.compositingVideoSinkProvider);
videoSink = compositingVideoSinkProvider.getSink(); videoSink = compositingVideoSinkProvider.getSink();
videoFrameReleaseControl =
checkStateNotNull(compositingVideoSinkProvider.getVideoFrameReleaseControl());
videoEffects = ImmutableList.of(); videoEffects = ImmutableList.of();
streamOffsetUs = C.TIME_UNSET; streamOffsetUs = C.TIME_UNSET;
} }
@ -357,11 +353,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public boolean isReady() { public boolean isReady() {
// If the renderer was enabled with mayRenderStartOfStream set to false, meaning the image if (mayRenderStartOfStream) {
// renderer is playing after a video, we don't need to wait until the first frame is rendered. // The image renderer is not playing after a video. We must wait until the first frame is
// If the renderer was enabled with mayRenderStartOfStream, we must wait until the first frame // rendered.
// is rendered, which is checked by VideoSink.isReady(). return videoSink.isReady(/* rendererOtherwiseReady= */ super.isReady());
return super.isReady() && (!mayRenderStartOfStream || videoSink.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 @Override