diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/DefaultVideoSink.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/DefaultVideoSink.java index 81d3cc0cda..e2fd77ffd0 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/DefaultVideoSink.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/DefaultVideoSink.java @@ -17,6 +17,8 @@ package androidx.media3.exoplayer.video; import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkStateNotNull; +import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_IMMEDIATELY; +import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_WHEN_STARTED; import android.graphics.Bitmap; import android.view.Surface; @@ -83,7 +85,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public void onRendererEnabled(boolean mayRenderStartOfStream) { - videoFrameReleaseControl.onEnabled(mayRenderStartOfStream); + int firstFrameReleaseInstruction = + mayRenderStartOfStream ? RELEASE_FIRST_FRAME_IMMEDIATELY : RELEASE_FIRST_FRAME_WHEN_STARTED; + videoFrameReleaseControl.onStreamChanged(firstFrameReleaseInstruction); } @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 0f1bcb5b07..4c80cd1eb6 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 @@ -22,6 +22,9 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED; import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED; import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_NO; +import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_IMMEDIATELY; +import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED; +import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_WHEN_STARTED; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static java.lang.Math.max; import static java.lang.Math.min; @@ -911,7 +914,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer } } else { videoFrameReleaseControl.setClock(getClock()); - videoFrameReleaseControl.onEnabled(mayRenderStartOfStream); + int firstFrameReleaseInstruction = + mayRenderStartOfStream + ? RELEASE_FIRST_FRAME_IMMEDIATELY + : RELEASE_FIRST_FRAME_WHEN_STARTED; + videoFrameReleaseControl.onStreamChanged(firstFrameReleaseInstruction); } } @@ -1837,7 +1844,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer videoSink.setStreamTimestampInfo( getOutputStreamStartPositionUs(), getBufferTimestampAdjustmentUs()); } else { - videoFrameReleaseControl.onProcessedStreamChange(); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED); } pendingVideoSinkInputStreamChange = true; maybeSetupTunnelingForFirstFrame(); 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 5656d2108e..bb012d6a2a 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 @@ -41,6 +41,35 @@ import java.lang.annotation.Target; @UnstableApi public final class VideoFrameReleaseControl { + /** + * The instruction provided to {@link #onStreamChanged(int)} for releasing the first frame. + * + *
One of {@link #RELEASE_FIRST_FRAME_IMMEDIATELY}, {@link #RELEASE_FIRST_FRAME_WHEN_STARTED} + * or {@link #RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED}. + */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @Target(TYPE_USE) + @UnstableApi + @IntDef({ + RELEASE_FIRST_FRAME_IMMEDIATELY, + RELEASE_FIRST_FRAME_WHEN_STARTED, + RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED + }) + public @interface FirstFrameReleaseInstruction {} + + /** Instructs to release the first frame as soon as possible. */ + public static final int RELEASE_FIRST_FRAME_IMMEDIATELY = 0; + + /** Instructs to release the first frame when rendering starts. */ + public static final int RELEASE_FIRST_FRAME_WHEN_STARTED = 1; + + /** + * Instructs to release the first frame when the playback position reaches the stream start + * position. + */ + public static final int RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED = 2; + /** * The frame release action returned by {@link #getFrameReleaseAction(long, long, long, long, * boolean, FrameReleaseInfo)}. @@ -206,12 +235,25 @@ public final class VideoFrameReleaseControl { clock = Clock.DEFAULT; } - /** Called when the renderer is enabled. */ - public void onEnabled(boolean releaseFirstFrameBeforeStarted) { - firstFrameState = - releaseFirstFrameBeforeStarted - ? C.FIRST_FRAME_NOT_RENDERED - : C.FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED; + /** + * Called when the stream changes. + * + *
Must also be called for the first stream. + */ + public void onStreamChanged(@FirstFrameReleaseInstruction int firstFrameReleaseInstruction) { + switch (firstFrameReleaseInstruction) { + case RELEASE_FIRST_FRAME_IMMEDIATELY: + firstFrameState = C.FIRST_FRAME_NOT_RENDERED; + break; + case RELEASE_FIRST_FRAME_WHEN_STARTED: + firstFrameState = C.FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED; + break; + case RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED: + lowerFirstFrameState(C.FIRST_FRAME_NOT_RENDERED_AFTER_STREAM_CHANGE); + break; + default: + throw new IllegalStateException(); + } } /** Called when the renderer is started. */ @@ -228,11 +270,6 @@ public final class VideoFrameReleaseControl { frameReleaseHelper.onStopped(); } - /** Called when the renderer processed a stream change. */ - public void onProcessedStreamChange() { - lowerFirstFrameState(C.FIRST_FRAME_NOT_RENDERED_AFTER_STREAM_CHANGE); - } - /** Called when the display surface changed. */ public void setOutputSurface(@Nullable Surface outputSurface) { frameReleaseHelper.onSurfaceChanged(outputSurface); 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 9163541c87..71e4db915a 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 @@ -17,6 +17,7 @@ package androidx.media3.exoplayer.video; import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkNotNull; +import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED; import androidx.annotation.Nullable; import androidx.media3.common.C; @@ -138,7 +139,8 @@ import androidx.media3.exoplayer.ExoPlaybackException; long presentationTimeUs = presentationTimestampsUs.element(); // Check whether this buffer comes with a new stream start position. if (maybeUpdateOutputStreamStartPosition(presentationTimeUs)) { - videoFrameReleaseControl.onProcessedStreamChange(); + videoFrameReleaseControl.onStreamChanged( + RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED); } @VideoFrameReleaseControl.FrameReleaseAction int frameReleaseAction = 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 6e6f8cfc86..5cbdc347e4 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 @@ -15,6 +15,8 @@ */ package androidx.media3.exoplayer.video; +import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_IMMEDIATELY; +import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_WHEN_STARTED; import static com.google.common.truth.Truth.assertThat; import androidx.media3.exoplayer.ExoPlaybackException; @@ -98,21 +100,12 @@ public class VideoFrameReleaseControlTest { } @Test - public void onFrameReleasedIsFirstFrame_resetsAfterOnEnabled() { + public void onFrameReleasedIsFirstFrame_resetsAfterOnStreamChanged() { VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl(); - assertThat(videoFrameReleaseControl.onFrameReleasedIsFirstFrame()).isTrue(); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); - - assertThat(videoFrameReleaseControl.onFrameReleasedIsFirstFrame()).isTrue(); - } - - @Test - public void onFrameReleasedIsFirstFrame_resetsAfterOnProcessedStreamChange() { - VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl(); - - assertThat(videoFrameReleaseControl.onFrameReleasedIsFirstFrame()).isTrue(); - videoFrameReleaseControl.onProcessedStreamChange(); + videoFrameReleaseControl.onFrameReleasedIsFirstFrame(); + assertThat(videoFrameReleaseControl.onFrameReleasedIsFirstFrame()).isFalse(); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); assertThat(videoFrameReleaseControl.onFrameReleasedIsFirstFrame()).isTrue(); } @@ -144,7 +137,7 @@ public class VideoFrameReleaseControlTest { VideoFrameReleaseControl.FrameReleaseInfo frameReleaseInfo = new VideoFrameReleaseControl.FrameReleaseInfo(); VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl(); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); assertThat( videoFrameReleaseControl.getFrameReleaseAction( @@ -163,7 +156,7 @@ public class VideoFrameReleaseControlTest { VideoFrameReleaseControl.FrameReleaseInfo frameReleaseInfo = new VideoFrameReleaseControl.FrameReleaseInfo(); VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl(); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ false); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_WHEN_STARTED); assertThat( videoFrameReleaseControl.getFrameReleaseAction( @@ -185,7 +178,7 @@ public class VideoFrameReleaseControlTest { FakeClock clock = new FakeClock(/* isAutoAdvancing= */ false); VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl(); videoFrameReleaseControl.setClock(clock); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ false); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_WHEN_STARTED); videoFrameReleaseControl.onStarted(); @@ -208,7 +201,7 @@ public class VideoFrameReleaseControlTest { FakeClock clock = new FakeClock(/* isAutoAdvancing= */ false); VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl(); videoFrameReleaseControl.setClock(clock); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); // First frame released. assertThat( @@ -242,7 +235,7 @@ public class VideoFrameReleaseControlTest { FakeClock clock = new FakeClock(/* isAutoAdvancing= */ false); VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl(); videoFrameReleaseControl.setClock(clock); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); videoFrameReleaseControl.onStarted(); // First frame released. @@ -277,7 +270,7 @@ public class VideoFrameReleaseControlTest { FakeClock clock = new FakeClock(/* isAutoAdvancing= */ false); VideoFrameReleaseControl videoFrameReleaseControl = createVideoFrameReleaseControl(); videoFrameReleaseControl.setClock(clock); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); videoFrameReleaseControl.onStarted(); @@ -320,7 +313,7 @@ public class VideoFrameReleaseControlTest { /* shouldIgnoreFrame= */ false), /* allowedJoiningTimeMs= */ 0); videoFrameReleaseControl.setClock(clock); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); videoFrameReleaseControl.onStarted(); @@ -365,7 +358,7 @@ public class VideoFrameReleaseControlTest { /* shouldIgnoreFrame= */ false), /* allowedJoiningTimeMs= */ 1234); videoFrameReleaseControl.setClock(clock); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); videoFrameReleaseControl.onStarted(); // Start joining. @@ -409,7 +402,7 @@ public class VideoFrameReleaseControlTest { /* shouldIgnoreFrame= */ false), /* allowedJoiningTimeMs= */ 1234); videoFrameReleaseControl.setClock(clock); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); videoFrameReleaseControl.onStarted(); // Start joining. @@ -452,7 +445,7 @@ public class VideoFrameReleaseControlTest { /* shouldIgnoreFrame= */ true), /* allowedJoiningTimeMs= */ 0); videoFrameReleaseControl.setClock(clock); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); videoFrameReleaseControl.onStarted(); 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 dc92b8c20d..aed04faa6d 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 @@ -15,6 +15,7 @@ */ package androidx.media3.exoplayer.video; +import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_IMMEDIATELY; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -44,7 +45,7 @@ public class VideoFrameRenderControlTest { VideoFrameRenderControl videoFrameRenderControl = new VideoFrameRenderControl(frameRenderer, videoFrameReleaseControl); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); videoFrameRenderControl.onVideoSizeChanged( /* width= */ VIDEO_WIDTH, /* height= */ VIDEO_HEIGHT); videoFrameRenderControl.onFrameAvailableForRendering(/* presentationTimeUs= */ 0); @@ -72,7 +73,7 @@ public class VideoFrameRenderControlTest { VideoFrameRenderControl videoFrameRenderControl = new VideoFrameRenderControl(frameRenderer, videoFrameReleaseControl); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); videoFrameReleaseControl.onStarted(); videoFrameRenderControl.onVideoSizeChanged( /* width= */ VIDEO_WIDTH, /* height= */ VIDEO_HEIGHT); @@ -117,7 +118,7 @@ public class VideoFrameRenderControlTest { VideoFrameRenderControl videoFrameRenderControl = new VideoFrameRenderControl(frameRenderer, videoFrameReleaseControl); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); videoFrameReleaseControl.onStarted(); videoFrameRenderControl.onVideoSizeChanged( /* width= */ VIDEO_WIDTH, /* height= */ VIDEO_HEIGHT); @@ -164,7 +165,7 @@ public class VideoFrameRenderControlTest { VideoFrameRenderControl videoFrameRenderControl = new VideoFrameRenderControl(frameRenderer, videoFrameReleaseControl); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); videoFrameReleaseControl.onStarted(); videoFrameRenderControl.onVideoSizeChanged( /* width= */ VIDEO_WIDTH, /* height= */ VIDEO_HEIGHT); @@ -202,7 +203,7 @@ public class VideoFrameRenderControlTest { VideoFrameRenderControl videoFrameRenderControl = new VideoFrameRenderControl(frameRenderer, videoFrameReleaseControl); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); videoFrameReleaseControl.onStarted(); videoFrameRenderControl.onVideoSizeChanged( /* width= */ VIDEO_WIDTH, /* height= */ VIDEO_HEIGHT); @@ -247,7 +248,7 @@ public class VideoFrameRenderControlTest { VideoFrameRenderControl videoFrameRenderControl = new VideoFrameRenderControl(frameRenderer, videoFrameReleaseControl); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); videoFrameRenderControl.onVideoSizeChanged( /* width= */ VIDEO_WIDTH, /* height= */ VIDEO_HEIGHT); videoFrameRenderControl.onFrameAvailableForRendering(/* presentationTimeUs= */ 0); @@ -265,7 +266,7 @@ public class VideoFrameRenderControlTest { VideoFrameRenderControl videoFrameRenderControl = new VideoFrameRenderControl(frameRenderer, videoFrameReleaseControl); - videoFrameReleaseControl.onEnabled(/* releaseFirstFrameBeforeStarted= */ true); + videoFrameReleaseControl.onStreamChanged(RELEASE_FIRST_FRAME_IMMEDIATELY); videoFrameRenderControl.onVideoSizeChanged( /* width= */ VIDEO_WIDTH, /* height= */ VIDEO_HEIGHT); videoFrameRenderControl.onFrameAvailableForRendering(/* presentationTimeUs= */ 0);