Remove implementation of DefaultVideoSink.onRendererEnabled

The goal is to get rid of VideoSink.onRendererEnabled so that this
interface becomes renderer-agnostic. Indeed, for multi video sequences,
the DefaultVideoSink won't receive input from a single renderer anymore.

This is a no-op refactoring

PiperOrigin-RevId: 738296515
This commit is contained in:
kimvde 2025-03-19 01:22:06 -07:00 committed by Copybara-Service
parent d0d76f214a
commit 14c06eaf8e
11 changed files with 134 additions and 70 deletions

View File

@ -17,9 +17,6 @@ package androidx.media3.exoplayer.video;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Assertions.checkStateNotNull; 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; import android.graphics.Bitmap;
import android.view.Surface; import android.view.Surface;
@ -82,11 +79,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
videoFrameMetadataListener = (presentationTimeUs, releaseTimeNs, format, mediaFormat) -> {}; videoFrameMetadataListener = (presentationTimeUs, releaseTimeNs, format, mediaFormat) -> {};
} }
/**
* {@inheritDoc}
*
* <p>This method will always throw an {@link UnsupportedOperationException}.
*/
@Override @Override
public void onRendererEnabled(boolean mayRenderStartOfStream) { public void onRendererEnabled(boolean mayRenderStartOfStream) {
int firstFrameReleaseInstruction = throw new UnsupportedOperationException();
mayRenderStartOfStream ? RELEASE_FIRST_FRAME_IMMEDIATELY : RELEASE_FIRST_FRAME_WHEN_STARTED;
videoFrameRenderControl.onStreamChanged(firstFrameReleaseInstruction, streamStartPositionUs);
} }
@Override @Override
@ -197,9 +197,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
videoFrameReleaseControl.setChangeFrameRateStrategy(changeFrameRateStrategy); videoFrameReleaseControl.setChangeFrameRateStrategy(changeFrameRateStrategy);
} }
/**
* {@inheritDoc}
*
* <p>This method will always throw an {@link UnsupportedOperationException}.
*/
@Override @Override
public void enableMayRenderStartOfStream() { public void enableMayRenderStartOfStream() {
videoFrameReleaseControl.allowReleaseFirstFrameBeforeStarted(); throw new UnsupportedOperationException();
} }
/** /**
@ -209,7 +214,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
*/ */
@Override @Override
public void onInputStreamChanged( public void onInputStreamChanged(
@InputType int inputType, Format format, long startPositionUs, List<Effect> videoEffects) { @InputType int inputType,
Format format,
long startPositionUs,
@FirstFrameReleaseInstruction int firstFrameReleaseInstruction,
List<Effect> videoEffects) {
checkState(videoEffects.isEmpty()); checkState(videoEffects.isEmpty());
if (format.width != inputFormat.width || format.height != inputFormat.height) { if (format.width != inputFormat.width || format.height != inputFormat.height) {
videoFrameRenderControl.onVideoSizeChanged(format.width, format.height); videoFrameRenderControl.onVideoSizeChanged(format.width, format.height);
@ -219,8 +228,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
inputFormat = format; inputFormat = format;
if (startPositionUs != this.streamStartPositionUs) { if (startPositionUs != this.streamStartPositionUs) {
videoFrameRenderControl.onStreamChanged( videoFrameRenderControl.onStreamChanged(firstFrameReleaseInstruction, startPositionUs);
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED, startPositionUs);
this.streamStartPositionUs = startPositionUs; this.streamStartPositionUs = startPositionUs;
} }
} }

View File

@ -22,9 +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_MAX_INPUT_SIZE_EXCEEDED;
import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_MAX_RESOLUTION_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.DecoderReuseEvaluation.REUSE_RESULT_NO;
import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_IMMEDIATELY; import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_IMMEDIATELY;
import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED; import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_WHEN_STARTED; import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_STARTED;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;
@ -1650,7 +1650,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
} }
/** /**
* Called when ready to {@linkplain VideoSink#onInputStreamChanged(int, Format, long, List) * Called when ready to {@linkplain VideoSink#onInputStreamChanged(int, Format, long, int, List)
* change} the input stream. * change} the input stream.
* *
* <p>The default implementation applies this renderer's video effects. * <p>The default implementation applies this renderer's video effects.
@ -1659,7 +1659,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
VideoSink videoSink, @VideoSink.InputType int inputType, Format format) { VideoSink videoSink, @VideoSink.InputType int inputType, Format format) {
List<Effect> videoEffectsToApply = videoEffects != null ? videoEffects : ImmutableList.of(); List<Effect> videoEffectsToApply = videoEffects != null ? videoEffects : ImmutableList.of();
videoSink.onInputStreamChanged( videoSink.onInputStreamChanged(
inputType, format, getOutputStreamStartPositionUs(), videoEffectsToApply); inputType,
format,
getOutputStreamStartPositionUs(),
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED,
videoEffectsToApply);
} }
@Override @Override

View File

@ -22,6 +22,7 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.contains; import static androidx.media3.common.util.Util.contains;
import static androidx.media3.common.util.Util.getMaxPendingFramesCountForMediaCodecDecoders; import static androidx.media3.common.util.Util.getMaxPendingFramesCountForMediaCodecDecoders;
import static androidx.media3.exoplayer.video.VideoSink.INPUT_TYPE_SURFACE; import static androidx.media3.exoplayer.video.VideoSink.INPUT_TYPE_SURFACE;
import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import android.content.Context; import android.content.Context;
@ -297,6 +298,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
private @MonotonicNonNull HandlerWrapper handler; private @MonotonicNonNull HandlerWrapper handler;
private @MonotonicNonNull VideoGraph videoGraph; private @MonotonicNonNull VideoGraph videoGraph;
private long outputStreamStartPositionUs; private long outputStreamStartPositionUs;
private @VideoSink.FirstFrameReleaseInstruction int nextFirstOutputFrameReleaseInstruction;
@Nullable private Pair<Surface, Size> currentSurfaceAndSize; @Nullable private Pair<Surface, Size> currentSurfaceAndSize;
private int pendingFlushCount; private int pendingFlushCount;
private @State int state; private @State int state;
@ -347,6 +349,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
listeners = new CopyOnWriteArraySet<>(); listeners = new CopyOnWriteArraySet<>();
requestOpenGlToneMapping = builder.requestOpenGlToneMapping; requestOpenGlToneMapping = builder.requestOpenGlToneMapping;
videoGraphOutputFormat = new Format.Builder().build(); videoGraphOutputFormat = new Format.Builder().build();
outputStreamStartPositionUs = C.TIME_UNSET;
lastOutputBufferPresentationTimeUs = C.TIME_UNSET; lastOutputBufferPresentationTimeUs = C.TIME_UNSET;
finalBufferPresentationTimeUs = C.TIME_UNSET; finalBufferPresentationTimeUs = C.TIME_UNSET;
totalVideoInputCount = C.LENGTH_UNSET; totalVideoInputCount = C.LENGTH_UNSET;
@ -425,13 +428,13 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
// We forward output size changes to the sink even if we are still flushing. // We forward output size changes to the sink even if we are still flushing.
videoGraphOutputFormat = videoGraphOutputFormat =
videoGraphOutputFormat.buildUpon().setWidth(width).setHeight(height).build(); videoGraphOutputFormat.buildUpon().setWidth(width).setHeight(height).build();
onOutputStreamChanged(); onOutputStreamChanged(nextFirstOutputFrameReleaseInstruction);
} }
@Override @Override
public void onOutputFrameRateChanged(float frameRate) { public void onOutputFrameRateChanged(float frameRate) {
videoGraphOutputFormat = videoGraphOutputFormat.buildUpon().setFrameRate(frameRate).build(); videoGraphOutputFormat = videoGraphOutputFormat.buildUpon().setFrameRate(frameRate).build();
onOutputStreamChanged(); onOutputStreamChanged(nextFirstOutputFrameReleaseInstruction);
} }
@Override @Override
@ -453,7 +456,8 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
if (newOutputStreamStartPositionUs != null if (newOutputStreamStartPositionUs != null
&& newOutputStreamStartPositionUs != outputStreamStartPositionUs) { && newOutputStreamStartPositionUs != outputStreamStartPositionUs) {
outputStreamStartPositionUs = newOutputStreamStartPositionUs; outputStreamStartPositionUs = newOutputStreamStartPositionUs;
onOutputStreamChanged(); onOutputStreamChanged(nextFirstOutputFrameReleaseInstruction);
nextFirstOutputFrameReleaseInstruction = RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
} }
boolean isLastFrame = boolean isLastFrame =
finalBufferPresentationTimeUs != C.TIME_UNSET finalBufferPresentationTimeUs != C.TIME_UNSET
@ -598,7 +602,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
if (streamStartPositionsUs.size() == 1) { if (streamStartPositionsUs.size() == 1) {
// Use the latest startPositionUs if none is passed after flushing. // Use the latest startPositionUs if none is passed after flushing.
outputStreamStartPositionUs = checkNotNull(streamStartPositionsUs.pollFirst()); outputStreamStartPositionUs = checkNotNull(streamStartPositionsUs.pollFirst());
onOutputStreamChanged(); onOutputStreamChanged(nextFirstOutputFrameReleaseInstruction);
} }
lastOutputBufferPresentationTimeUs = C.TIME_UNSET; lastOutputBufferPresentationTimeUs = C.TIME_UNSET;
finalBufferPresentationTimeUs = C.TIME_UNSET; finalBufferPresentationTimeUs = C.TIME_UNSET;
@ -635,11 +639,13 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
return inputColorInfo; return inputColorInfo;
} }
private void onOutputStreamChanged() { private void onOutputStreamChanged(
@VideoSink.FirstFrameReleaseInstruction int firstFrameReleaseInstruction) {
defaultVideoSink.onInputStreamChanged( defaultVideoSink.onInputStreamChanged(
INPUT_TYPE_SURFACE, INPUT_TYPE_SURFACE,
videoGraphOutputFormat, videoGraphOutputFormat,
outputStreamStartPositionUs, outputStreamStartPositionUs,
firstFrameReleaseInstruction,
/* videoEffects= */ ImmutableList.of()); /* videoEffects= */ ImmutableList.of());
} }
@ -680,7 +686,10 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
@Override @Override
public void onRendererEnabled(boolean mayRenderStartOfStream) { public void onRendererEnabled(boolean mayRenderStartOfStream) {
defaultVideoSink.onRendererEnabled(mayRenderStartOfStream); nextFirstOutputFrameReleaseInstruction =
mayRenderStartOfStream
? RELEASE_FIRST_FRAME_IMMEDIATELY
: RELEASE_FIRST_FRAME_WHEN_STARTED;
} }
@Override @Override
@ -765,7 +774,11 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
@Override @Override
public void onInputStreamChanged( public void onInputStreamChanged(
@InputType int inputType, Format format, long startPositionUs, List<Effect> videoEffects) { @InputType int inputType,
Format format,
long startPositionUs,
@FirstFrameReleaseInstruction int firstFrameReleaseInstruction,
List<Effect> videoEffects) {
checkState(isInitialized()); checkState(isInitialized());
switch (inputType) { switch (inputType) {
case INPUT_TYPE_SURFACE: case INPUT_TYPE_SURFACE:
@ -844,7 +857,9 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
@Override @Override
public void enableMayRenderStartOfStream() { public void enableMayRenderStartOfStream() {
defaultVideoSink.enableMayRenderStartOfStream(); if (nextFirstOutputFrameReleaseInstruction == RELEASE_FIRST_FRAME_WHEN_STARTED) {
nextFirstOutputFrameReleaseInstruction = RELEASE_FIRST_FRAME_IMMEDIATELY;
}
} }
@Override @Override

View File

@ -17,6 +17,9 @@ package androidx.media3.exoplayer.video;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Util.msToUs; import static androidx.media3.common.util.Util.msToUs;
import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_IMMEDIATELY;
import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_STARTED;
import static java.lang.Math.min; import static java.lang.Math.min;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
@ -40,35 +43,6 @@ import java.lang.annotation.Target;
@UnstableApi @UnstableApi
public final class VideoFrameReleaseControl { public final class VideoFrameReleaseControl {
/**
* The instruction provided to {@link #onStreamChanged(int)} for releasing the first frame.
*
* <p>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, * The frame release action returned by {@link #getFrameReleaseAction(long, long, long, long,
* boolean, boolean, FrameReleaseInfo)}. * boolean, boolean, FrameReleaseInfo)}.
@ -241,7 +215,8 @@ public final class VideoFrameReleaseControl {
* *
* <p>Must also be called for the first stream. * <p>Must also be called for the first stream.
*/ */
public void onStreamChanged(@FirstFrameReleaseInstruction int firstFrameReleaseInstruction) { public void onStreamChanged(
@VideoSink.FirstFrameReleaseInstruction int firstFrameReleaseInstruction) {
switch (firstFrameReleaseInstruction) { switch (firstFrameReleaseInstruction) {
case RELEASE_FIRST_FRAME_IMMEDIATELY: case RELEASE_FIRST_FRAME_IMMEDIATELY:
firstFrameState = C.FIRST_FRAME_NOT_RENDERED; firstFrameState = C.FIRST_FRAME_NOT_RENDERED;

View File

@ -17,7 +17,7 @@ package androidx.media3.exoplayer.video;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED; import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
@ -179,7 +179,7 @@ import androidx.media3.exoplayer.ExoPlaybackException;
} }
public void onStreamChanged( public void onStreamChanged(
@VideoFrameReleaseControl.FirstFrameReleaseInstruction int firstFrameReleaseInstruction, @VideoSink.FirstFrameReleaseInstruction int firstFrameReleaseInstruction,
long streamStartPositionUs) { long streamStartPositionUs) {
if (presentationTimestampsUs.isEmpty()) { if (presentationTimestampsUs.isEmpty()) {
videoFrameReleaseControl.onStreamChanged(firstFrameReleaseInstruction); videoFrameReleaseControl.onStreamChanged(firstFrameReleaseInstruction);

View File

@ -125,6 +125,35 @@ public interface VideoSink {
/** Input frames come from a {@link Bitmap}. */ /** Input frames come from a {@link Bitmap}. */
int INPUT_TYPE_BITMAP = 2; int INPUT_TYPE_BITMAP = 2;
/**
* The instruction provided when the stream changes for releasing the first frame.
*
* <p>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
})
@interface FirstFrameReleaseInstruction {}
/** Instructs to release the first frame as soon as possible. */
int RELEASE_FIRST_FRAME_IMMEDIATELY = 0;
/** Instructs to release the first frame when rendering starts. */
int RELEASE_FIRST_FRAME_WHEN_STARTED = 1;
/**
* Instructs to release the first frame when the playback position reaches the stream start
* position.
*/
int RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED = 2;
/** Called when the {@link Renderer} currently feeding this sink is enabled. */ /** Called when the {@link Renderer} currently feeding this sink is enabled. */
void onRendererEnabled(boolean mayRenderStartOfStream); void onRendererEnabled(boolean mayRenderStartOfStream);
@ -188,7 +217,7 @@ public interface VideoSink {
* *
* <p>This method returns {@code true} if the end of the last input stream has been {@linkplain * <p>This method returns {@code true} if the end of the last input stream has been {@linkplain
* #signalEndOfCurrentInputStream() signaled} and all the input frames have been rendered. Note * #signalEndOfCurrentInputStream() signaled} and all the input frames have been rendered. Note
* that a new input stream can be {@linkplain #onInputStreamChanged(int, Format, long, List) * that a new input stream can be {@linkplain #onInputStreamChanged(int, Format, long, int, List)
* signaled} even when this method returns true (in which case the sink will not be ended * signaled} even when this method returns true (in which case the sink will not be ended
* anymore). * anymore).
*/ */
@ -250,16 +279,22 @@ public interface VideoSink {
* @param format The {@link Format} of the stream. * @param format The {@link Format} of the stream.
* @param startPositionUs The start position of the buffer presentation timestamps of the stream, * @param startPositionUs The start position of the buffer presentation timestamps of the stream,
* in microseconds. * in microseconds.
* @param firstFrameReleaseInstruction The {@link FirstFrameReleaseInstruction} indicating when to
* release the stream's first frame.
* @param videoEffects The {@link List<Effect>} to apply to the new stream. * @param videoEffects The {@link List<Effect>} to apply to the new stream.
*/ */
void onInputStreamChanged( void onInputStreamChanged(
@InputType int inputType, Format format, long startPositionUs, List<Effect> videoEffects); @InputType int inputType,
Format format,
long startPositionUs,
@FirstFrameReleaseInstruction int firstFrameReleaseInstruction,
List<Effect> videoEffects);
/** /**
* Handles a video input frame. * Handles a video input frame.
* *
* <p>Must be called after the corresponding stream is {@linkplain #onInputStreamChanged(int, * <p>Must be called after the corresponding stream is {@linkplain #onInputStreamChanged(int,
* Format, long, List) signaled}. * Format, long, int, List) signaled}.
* *
* @param framePresentationTimeUs The frame's presentation time, in microseconds. * @param framePresentationTimeUs The frame's presentation time, in microseconds.
* @param isLastFrame Whether this is the last frame of the video stream. This flag is set on a * @param isLastFrame Whether this is the last frame of the video stream. This flag is set on a
@ -276,7 +311,7 @@ public interface VideoSink {
* Handles an input {@link Bitmap}. * Handles an input {@link Bitmap}.
* *
* <p>Must be called after the corresponding stream is {@linkplain #onInputStreamChanged(int, * <p>Must be called after the corresponding stream is {@linkplain #onInputStreamChanged(int,
* Format, long, List) signaled}. * Format, long, int, List) signaled}.
* *
* @param inputBitmap The {@link Bitmap} to queue to the video sink. * @param inputBitmap The {@link Bitmap} to queue to the video sink.
* @param timestampIterator The times within the current stream that the bitmap should be shown * @param timestampIterator The times within the current stream that the bitmap should be shown

View File

@ -15,6 +15,8 @@
*/ */
package androidx.media3.exoplayer.video; package androidx.media3.exoplayer.video;
import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_IMMEDIATELY;
import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@ -80,10 +82,24 @@ public final class PlaybackVideoGraphWrapperTest {
sink.initialize(format); sink.initialize(format);
sink.onInputStreamChanged(VideoSink.INPUT_TYPE_SURFACE, format, startPositionUs, firstEffects);
sink.onInputStreamChanged(VideoSink.INPUT_TYPE_SURFACE, format, startPositionUs, secondEffects);
sink.onInputStreamChanged( sink.onInputStreamChanged(
VideoSink.INPUT_TYPE_SURFACE, format, startPositionUs, ImmutableList.of()); VideoSink.INPUT_TYPE_SURFACE,
format,
startPositionUs,
RELEASE_FIRST_FRAME_IMMEDIATELY,
firstEffects);
sink.onInputStreamChanged(
VideoSink.INPUT_TYPE_SURFACE,
format,
startPositionUs,
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED,
secondEffects);
sink.onInputStreamChanged(
VideoSink.INPUT_TYPE_SURFACE,
format,
startPositionUs,
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED,
ImmutableList.of());
testVideoGraphFactory.verifyRegisteredEffectsMatches(/* invocationTimes= */ 3); testVideoGraphFactory.verifyRegisteredEffectsMatches(/* invocationTimes= */ 3);
assertThat(testVideoGraphFactory.getCapturedEffects()) assertThat(testVideoGraphFactory.getCapturedEffects())
.isEqualTo(ImmutableList.of(firstEffects, secondEffects, ImmutableList.of())); .isEqualTo(ImmutableList.of(firstEffects, secondEffects, ImmutableList.of()));

View File

@ -15,8 +15,8 @@
*/ */
package androidx.media3.exoplayer.video; package androidx.media3.exoplayer.video;
import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_IMMEDIATELY; import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_IMMEDIATELY;
import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_WHEN_STARTED; import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_STARTED;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
@ -587,7 +587,7 @@ public class VideoFrameReleaseControlTest {
/* isDecodeOnlyFrame= */ true, /* isDecodeOnlyFrame= */ true,
/* isLastFrame= */ true, /* isLastFrame= */ true,
frameReleaseInfo)) frameReleaseInfo))
.isEqualTo(VideoFrameReleaseControl.RELEASE_FIRST_FRAME_IMMEDIATELY); .isEqualTo(RELEASE_FIRST_FRAME_IMMEDIATELY);
} }
@Test @Test

View File

@ -15,8 +15,8 @@
*/ */
package androidx.media3.exoplayer.video; package androidx.media3.exoplayer.video;
import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_IMMEDIATELY; import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_IMMEDIATELY;
import static androidx.media3.exoplayer.video.VideoFrameReleaseControl.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED; import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;

View File

@ -220,10 +220,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public void onInputStreamChanged( public void onInputStreamChanged(
@InputType int inputType, Format format, long startPositionUs, List<Effect> videoEffects) { @InputType int inputType,
Format format,
long startPositionUs,
@FirstFrameReleaseInstruction int firstFrameReleaseInstruction,
List<Effect> videoEffects) {
executeOrDelay( executeOrDelay(
videoSink -> videoSink ->
videoSink.onInputStreamChanged(inputType, format, startPositionUs, videoEffects)); videoSink.onInputStreamChanged(
inputType, format, startPositionUs, firstFrameReleaseInstruction, videoEffects));
} }
/** /**

View File

@ -24,6 +24,7 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.SDK_INT; import static androidx.media3.common.util.Util.SDK_INT;
import static androidx.media3.exoplayer.DefaultRenderersFactory.DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS; import static androidx.media3.exoplayer.DefaultRenderersFactory.DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS;
import static androidx.media3.exoplayer.DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY; import static androidx.media3.exoplayer.DefaultRenderersFactory.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY;
import static androidx.media3.exoplayer.video.VideoSink.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -436,7 +437,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
protected void changeVideoSinkInputStream( protected void changeVideoSinkInputStream(
VideoSink videoSink, @VideoSink.InputType int inputType, Format format) { VideoSink videoSink, @VideoSink.InputType int inputType, Format format) {
videoSink.onInputStreamChanged( videoSink.onInputStreamChanged(
inputType, format, getOutputStreamStartPositionUs(), pendingEffects); inputType,
format,
getOutputStreamStartPositionUs(),
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED,
pendingEffects);
} }
private void activateBufferingVideoSink() { private void activateBufferingVideoSink() {
@ -625,6 +630,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
.setFrameRate(/* frameRate= */ DEFAULT_FRAME_RATE) .setFrameRate(/* frameRate= */ DEFAULT_FRAME_RATE)
.build(), .build(),
streamStartPositionUs, streamStartPositionUs,
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED,
videoEffects); videoEffects);
inputStreamPending = false; inputStreamPending = false;
} }