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 e2fd77ffd0..493c3f8211 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 @@ -18,6 +18,7 @@ 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_PREVIOUS_STREAM_PROCESSED; import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_WHEN_STARTED; import android.graphics.Bitmap; @@ -87,7 +88,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public void onRendererEnabled(boolean mayRenderStartOfStream) { int firstFrameReleaseInstruction = mayRenderStartOfStream ? RELEASE_FIRST_FRAME_IMMEDIATELY : RELEASE_FIRST_FRAME_WHEN_STARTED; - videoFrameReleaseControl.onStreamChanged(firstFrameReleaseInstruction); + videoFrameRenderControl.onStreamChanged(firstFrameReleaseInstruction, streamStartPositionUs); } @Override @@ -179,7 +180,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @Override public void setStreamTimestampInfo(long streamStartPositionUs, long bufferTimestampAdjustmentUs) { if (streamStartPositionUs != this.streamStartPositionUs) { - videoFrameRenderControl.onStreamStartPositionChanged(streamStartPositionUs); + videoFrameRenderControl.onStreamChanged( + RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED, streamStartPositionUs); this.streamStartPositionUs = streamStartPositionUs; } this.bufferTimestampAdjustmentUs = bufferTimestampAdjustmentUs; 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 4f10dff7ed..2128547f24 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 @@ -110,14 +110,10 @@ import androidx.media3.exoplayer.ExoPlaybackException; lastPresentationTimeUs = C.TIME_UNSET; if (streamStartPositionsUs.size() > 0) { // There is a pending streaming start position change. If seeking within the same stream, keep - // the pending start position with min timestamp to ensure the start position is applied on + // the pending start position with max timestamp to ensure the start position is applied on // the frames after flushing. Otherwise if seeking to another stream, a new start position // will be set before a new frame arrives so we'll be able to apply the new start position. - long lastStartPositionUs = getLastAndClear(streamStartPositionsUs); - // Input timestamps should always be positive because they are offset by ExoPlayer. Adding a - // position to the queue with timestamp 0 should therefore always apply it as long as it is - // the only position in the queue. - streamStartPositionsUs.add(/* timestamp= */ 0, lastStartPositionUs); + outputStreamStartPositionUs = getLastAndClear(streamStartPositionsUs); } if (videoSizes.size() > 0) { // Do not clear the last pending video size, we still want to report the size change after a @@ -137,8 +133,8 @@ import androidx.media3.exoplayer.ExoPlaybackException; public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { while (!presentationTimestampsUs.isEmpty()) { long presentationTimeUs = presentationTimestampsUs.element(); - // Check whether this buffer comes with a new stream start position. - if (maybeUpdateOutputStreamStartPosition(presentationTimeUs)) { + // Check whether this buffer comes with a new stream. + if (maybeUpdateOutputStream(presentationTimeUs)) { videoFrameReleaseControl.onStreamChanged( RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED); } @@ -182,10 +178,17 @@ import androidx.media3.exoplayer.ExoPlaybackException; new VideoSize(width, height)); } - public void onStreamStartPositionChanged(long streamStartPositionUs) { - streamStartPositionsUs.add( - latestInputPresentationTimeUs == C.TIME_UNSET ? 0 : latestInputPresentationTimeUs + 1, - streamStartPositionUs); + public void onStreamChanged( + @VideoFrameReleaseControl.FirstFrameReleaseInstruction int firstFrameReleaseInstruction, + long streamStartPositionUs) { + if (presentationTimestampsUs.isEmpty()) { + videoFrameReleaseControl.onStreamChanged(firstFrameReleaseInstruction); + outputStreamStartPositionUs = streamStartPositionUs; + } else { + streamStartPositionsUs.add( + latestInputPresentationTimeUs == C.TIME_UNSET ? 0 : latestInputPresentationTimeUs + 1, + streamStartPositionUs); + } } /** @@ -242,7 +245,7 @@ import androidx.media3.exoplayer.ExoPlaybackException; renderTimeNs, presentationTimeUs, videoFrameReleaseControl.onFrameReleasedIsFirstFrame()); } - private boolean maybeUpdateOutputStreamStartPosition(long presentationTimeUs) { + private boolean maybeUpdateOutputStream(long presentationTimeUs) { @Nullable Long newOutputStreamStartPositionUs = streamStartPositionsUs.pollFloor(presentationTimeUs); if (newOutputStreamStartPositionUs != null 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 aed04faa6d..6fa7a8d9c8 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 @@ -16,6 +16,7 @@ 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_PREVIOUS_STREAM_PROCESSED; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -122,7 +123,8 @@ public class VideoFrameRenderControlTest { videoFrameReleaseControl.onStarted(); videoFrameRenderControl.onVideoSizeChanged( /* width= */ VIDEO_WIDTH, /* height= */ VIDEO_HEIGHT); - videoFrameRenderControl.onStreamStartPositionChanged(/* streamStartPositionUs= */ 10_000); + videoFrameRenderControl.onStreamChanged( + RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED, /* streamStartPositionUs= */ 10_000); videoFrameRenderControl.onFrameAvailableForRendering(/* presentationTimeUs= */ 0); videoFrameRenderControl.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); @@ -136,7 +138,8 @@ public class VideoFrameRenderControlTest { // 10 milliseconds pass clock.advanceTime(/* timeDiffMs= */ 10); - videoFrameRenderControl.onStreamStartPositionChanged(/* streamStartPositionUs= */ 20_000); + videoFrameRenderControl.onStreamChanged( + RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED, /* streamStartPositionUs= */ 20_000); videoFrameRenderControl.onFrameAvailableForRendering(/* presentationTimeUs= */ 10_000); videoFrameRenderControl.render(/* positionUs= */ 10_000, /* elapsedRealtimeUs= */ 0);