mirror of
https://github.com/androidx/media.git
synced 2025-04-29 22:36:54 +08:00
Remove VideoSink.onRendererEnabled
This is part of the effort to make VideoSink independent from renderers. PiperOrigin-RevId: 740344126
This commit is contained in:
parent
a220b0cb5e
commit
2642d895bd
@ -79,16 +79,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
videoFrameMetadataListener = (presentationTimeUs, releaseTimeNs, format, mediaFormat) -> {};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>This method will always throw an {@link UnsupportedOperationException}.
|
||||
*/
|
||||
@Override
|
||||
public void onRendererEnabled(boolean mayRenderStartOfStream) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRendererStarted() {
|
||||
videoFrameReleaseControl.onStarted();
|
||||
@ -197,16 +187,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
videoFrameReleaseControl.setChangeFrameRateStrategy(changeFrameRateStrategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>This method will always throw an {@link UnsupportedOperationException}.
|
||||
*/
|
||||
@Override
|
||||
public void enableMayRenderStartOfStream() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
@ -233,6 +213,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allowReleaseFirstFrameBeforeStarted() {
|
||||
videoFrameReleaseControl.allowReleaseFirstFrameBeforeStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleInputFrame(
|
||||
long framePresentationTimeUs, boolean isLastFrame, VideoFrameHandler videoFrameHandler) {
|
||||
|
@ -190,6 +190,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
||||
private boolean codecHandlesHdr10PlusOutOfBandMetadata;
|
||||
private @MonotonicNonNull VideoSink videoSink;
|
||||
private boolean hasSetVideoSink;
|
||||
private @VideoSink.FirstFrameReleaseInstruction int nextVideoSinkFirstFrameReleaseInstruction;
|
||||
private @MonotonicNonNull List<Effect> videoEffects;
|
||||
@Nullable private Surface displaySurface;
|
||||
@Nullable private PlaceholderSurface placeholderSurface;
|
||||
@ -928,7 +929,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
||||
if (videoEffects != null) {
|
||||
videoSink.setVideoEffects(videoEffects);
|
||||
}
|
||||
videoSink.onRendererEnabled(mayRenderStartOfStream);
|
||||
nextVideoSinkFirstFrameReleaseInstruction =
|
||||
mayRenderStartOfStream
|
||||
? RELEASE_FIRST_FRAME_IMMEDIATELY
|
||||
: RELEASE_FIRST_FRAME_WHEN_STARTED;
|
||||
@Nullable WakeupListener wakeupListener = getWakeupListener();
|
||||
if (wakeupListener != null) {
|
||||
videoSink.setWakeupListener(wakeupListener);
|
||||
@ -956,7 +960,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
||||
@Override
|
||||
public void enableMayRenderStartOfStream() {
|
||||
if (videoSink != null) {
|
||||
videoSink.enableMayRenderStartOfStream();
|
||||
if (nextVideoSinkFirstFrameReleaseInstruction == RELEASE_FIRST_FRAME_IMMEDIATELY
|
||||
|| nextVideoSinkFirstFrameReleaseInstruction == RELEASE_FIRST_FRAME_WHEN_STARTED) {
|
||||
// The first stream change hasn't been queued to the sink.
|
||||
nextVideoSinkFirstFrameReleaseInstruction = RELEASE_FIRST_FRAME_IMMEDIATELY;
|
||||
} else {
|
||||
videoSink.allowReleaseFirstFrameBeforeStarted();
|
||||
}
|
||||
} else {
|
||||
videoFrameReleaseControl.allowReleaseFirstFrameBeforeStarted();
|
||||
}
|
||||
@ -1642,7 +1652,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
||||
.setWidth(width)
|
||||
.setHeight(height)
|
||||
.setPixelWidthHeightRatio(pixelWidthHeightRatio)
|
||||
.build());
|
||||
.build(),
|
||||
nextVideoSinkFirstFrameReleaseInstruction);
|
||||
nextVideoSinkFirstFrameReleaseInstruction =
|
||||
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
|
||||
} else {
|
||||
videoFrameReleaseControl.setFrameRate(format.frameRate);
|
||||
}
|
||||
@ -1656,13 +1669,16 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
||||
* <p>The default implementation applies this renderer's video effects.
|
||||
*/
|
||||
protected void changeVideoSinkInputStream(
|
||||
VideoSink videoSink, @VideoSink.InputType int inputType, Format format) {
|
||||
VideoSink videoSink,
|
||||
@VideoSink.InputType int inputType,
|
||||
Format format,
|
||||
@VideoSink.FirstFrameReleaseInstruction int firstFrameReleaseInstruction) {
|
||||
List<Effect> videoEffectsToApply = videoEffects != null ? videoEffects : ImmutableList.of();
|
||||
videoSink.onInputStreamChanged(
|
||||
inputType,
|
||||
format,
|
||||
getOutputStreamStartPositionUs(),
|
||||
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED,
|
||||
firstFrameReleaseInstruction,
|
||||
videoEffectsToApply);
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,6 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
import static androidx.media3.common.util.Util.contains;
|
||||
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.RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
|
||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||
|
||||
import android.content.Context;
|
||||
@ -281,12 +280,6 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
|
||||
private final Context context;
|
||||
|
||||
/**
|
||||
* A queue of unprocessed input frame start positions. Each position is associated with the
|
||||
* timestamp from which it should be applied.
|
||||
*/
|
||||
private final TimedValueQueue<Long> streamStartPositionsUs;
|
||||
|
||||
private final VideoGraph.Factory videoGraphFactory;
|
||||
private final SparseArray<InputVideoSink> inputVideoSinks;
|
||||
private final List<Effect> compositionEffects;
|
||||
@ -297,12 +290,18 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
private final CopyOnWriteArraySet<PlaybackVideoGraphWrapper.Listener> listeners;
|
||||
private final boolean requestOpenGlToneMapping;
|
||||
|
||||
/**
|
||||
* A queue of unprocessed stream changes. Each stream change is associated with the timestamp from
|
||||
* which it should be applied.
|
||||
*/
|
||||
private TimedValueQueue<StreamChangeInfo> pendingStreamChanges;
|
||||
|
||||
private Format videoGraphOutputFormat;
|
||||
private @MonotonicNonNull HandlerWrapper handler;
|
||||
private @MonotonicNonNull VideoGraph videoGraph;
|
||||
private @MonotonicNonNull VideoFrameMetadataListener videoFrameMetadataListener;
|
||||
private long outputStreamStartPositionUs;
|
||||
private @VideoSink.FirstFrameReleaseInstruction int nextFirstOutputFrameReleaseInstruction;
|
||||
private @VideoSink.FirstFrameReleaseInstruction int outputStreamFirstFrameReleaseInstruction;
|
||||
@Nullable private Pair<Surface, Size> currentSurfaceAndSize;
|
||||
private int pendingFlushCount;
|
||||
private @State int state;
|
||||
@ -331,7 +330,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
|
||||
private PlaybackVideoGraphWrapper(Builder builder) {
|
||||
context = builder.context;
|
||||
streamStartPositionsUs = new TimedValueQueue<>();
|
||||
pendingStreamChanges = new TimedValueQueue<>();
|
||||
videoGraphFactory = checkStateNotNull(builder.videoGraphFactory);
|
||||
inputVideoSinks = new SparseArray<>();
|
||||
compositionEffects = builder.compositionEffects;
|
||||
@ -432,13 +431,13 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
// We forward output size changes to the sink even if we are still flushing.
|
||||
videoGraphOutputFormat =
|
||||
videoGraphOutputFormat.buildUpon().setWidth(width).setHeight(height).build();
|
||||
onOutputStreamChanged(nextFirstOutputFrameReleaseInstruction);
|
||||
onOutputStreamChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOutputFrameRateChanged(float frameRate) {
|
||||
videoGraphOutputFormat = videoGraphOutputFormat.buildUpon().setFrameRate(frameRate).build();
|
||||
onOutputStreamChanged(nextFirstOutputFrameReleaseInstruction);
|
||||
onOutputStreamChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -469,13 +468,11 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
// The frame presentation time is relative to the start of the Composition and without the
|
||||
// renderer offset
|
||||
lastOutputBufferPresentationTimeUs = bufferPresentationTimeUs;
|
||||
Long newOutputStreamStartPositionUs =
|
||||
streamStartPositionsUs.pollFloor(bufferPresentationTimeUs);
|
||||
if (newOutputStreamStartPositionUs != null
|
||||
&& newOutputStreamStartPositionUs != outputStreamStartPositionUs) {
|
||||
outputStreamStartPositionUs = newOutputStreamStartPositionUs;
|
||||
onOutputStreamChanged(nextFirstOutputFrameReleaseInstruction);
|
||||
nextFirstOutputFrameReleaseInstruction = RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
|
||||
StreamChangeInfo streamChangeInfo = pendingStreamChanges.pollFloor(bufferPresentationTimeUs);
|
||||
if (streamChangeInfo != null) {
|
||||
outputStreamStartPositionUs = streamChangeInfo.startPositionUs;
|
||||
outputStreamFirstFrameReleaseInstruction = streamChangeInfo.firstFrameReleaseInstruction;
|
||||
onOutputStreamChanged();
|
||||
}
|
||||
boolean isLastFrame =
|
||||
finalBufferPresentationTimeUs != C.TIME_UNSET
|
||||
@ -623,13 +620,15 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
}
|
||||
pendingFlushCount++;
|
||||
defaultVideoSink.flush(resetPosition);
|
||||
while (streamStartPositionsUs.size() > 1) {
|
||||
streamStartPositionsUs.pollFirst();
|
||||
while (pendingStreamChanges.size() > 1) {
|
||||
pendingStreamChanges.pollFirst();
|
||||
}
|
||||
if (streamStartPositionsUs.size() == 1) {
|
||||
// Use the latest startPositionUs if none is passed after flushing.
|
||||
outputStreamStartPositionUs = checkNotNull(streamStartPositionsUs.pollFirst());
|
||||
onOutputStreamChanged(nextFirstOutputFrameReleaseInstruction);
|
||||
if (pendingStreamChanges.size() == 1) {
|
||||
// Use the latest stream change info if none is passed after flushing.
|
||||
StreamChangeInfo streamChangeInfo = checkNotNull(pendingStreamChanges.pollFirst());
|
||||
outputStreamStartPositionUs = streamChangeInfo.startPositionUs;
|
||||
outputStreamFirstFrameReleaseInstruction = streamChangeInfo.firstFrameReleaseInstruction;
|
||||
onOutputStreamChanged();
|
||||
}
|
||||
lastOutputBufferPresentationTimeUs = C.TIME_UNSET;
|
||||
finalBufferPresentationTimeUs = C.TIME_UNSET;
|
||||
@ -667,13 +666,12 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
return inputColorInfo;
|
||||
}
|
||||
|
||||
private void onOutputStreamChanged(
|
||||
@VideoSink.FirstFrameReleaseInstruction int firstFrameReleaseInstruction) {
|
||||
private void onOutputStreamChanged() {
|
||||
defaultVideoSink.onInputStreamChanged(
|
||||
INPUT_TYPE_SURFACE,
|
||||
videoGraphOutputFormat,
|
||||
outputStreamStartPositionUs,
|
||||
firstFrameReleaseInstruction,
|
||||
outputStreamFirstFrameReleaseInstruction,
|
||||
/* videoEffects= */ ImmutableList.of());
|
||||
}
|
||||
|
||||
@ -712,14 +710,6 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
listenerExecutor = NO_OP_EXECUTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRendererEnabled(boolean mayRenderStartOfStream) {
|
||||
nextFirstOutputFrameReleaseInstruction =
|
||||
mayRenderStartOfStream
|
||||
? RELEASE_FIRST_FRAME_IMMEDIATELY
|
||||
: RELEASE_FIRST_FRAME_WHEN_STARTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRendererStarted() {
|
||||
defaultVideoSink.onRendererStarted();
|
||||
@ -808,12 +798,8 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
@FirstFrameReleaseInstruction int firstFrameReleaseInstruction,
|
||||
List<Effect> videoEffects) {
|
||||
checkState(isInitialized());
|
||||
switch (inputType) {
|
||||
case INPUT_TYPE_SURFACE:
|
||||
case INPUT_TYPE_BITMAP:
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported input type " + inputType);
|
||||
if (inputType != INPUT_TYPE_SURFACE && inputType != INPUT_TYPE_BITMAP) {
|
||||
throw new UnsupportedOperationException("Unsupported input type " + inputType);
|
||||
}
|
||||
setPendingVideoEffects(videoEffects);
|
||||
this.inputType = inputType;
|
||||
@ -822,11 +808,56 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
hasSignaledEndOfCurrentInputStream = false;
|
||||
registerInputStream(format);
|
||||
// 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(
|
||||
lastBufferPresentationTimeUs == C.TIME_UNSET ? 0 : lastBufferPresentationTimeUs + 1,
|
||||
startPositionUs);
|
||||
// stream change info to the queue with timestamp 0 should therefore always apply it as long
|
||||
// as it is the only one in the queue.
|
||||
long fromTimestampUs =
|
||||
lastBufferPresentationTimeUs == C.TIME_UNSET ? 0 : lastBufferPresentationTimeUs + 1;
|
||||
pendingStreamChanges.add(
|
||||
fromTimestampUs,
|
||||
new StreamChangeInfo(startPositionUs, firstFrameReleaseInstruction, fromTimestampUs));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allowReleaseFirstFrameBeforeStarted() {
|
||||
// We know that this sink is connected to renderers. Each renderer will first queue a stream
|
||||
// change that has firstFrameReleaseInstruction set to either RELEASE_FIRST_FRAME_IMMEDIATELY
|
||||
// or RELEASE_FIRST_FRAME_WHEN_STARTED, and then queue stream changes that have
|
||||
// firstFrameReleaseInstruction set to RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED.
|
||||
// When a renderer queues the first stream change, all previous streams should have been fully
|
||||
// processed.
|
||||
// We want to release the first frame immediately if the firstFrameReleaseInstruction of the
|
||||
// first stream change queued by the current renderer was RELEASE_FIRST_FRAME_WHEN_STARTED and
|
||||
// the first frame hasn't been released yet.
|
||||
if (pendingStreamChanges.size() == 0) {
|
||||
// All the stream changes have already been processed by the VideoGraph. Delegate to the
|
||||
// downstream component.
|
||||
defaultVideoSink.allowReleaseFirstFrameBeforeStarted();
|
||||
return;
|
||||
}
|
||||
TimedValueQueue<StreamChangeInfo> newPendingStreamChanges = new TimedValueQueue<>();
|
||||
boolean isFirstStreamChange = true;
|
||||
while (pendingStreamChanges.size() > 0) {
|
||||
StreamChangeInfo streamChangeInfo = checkNotNull(pendingStreamChanges.pollFirst());
|
||||
if (isFirstStreamChange) {
|
||||
if (streamChangeInfo.firstFrameReleaseInstruction == RELEASE_FIRST_FRAME_IMMEDIATELY
|
||||
|| streamChangeInfo.firstFrameReleaseInstruction
|
||||
== RELEASE_FIRST_FRAME_WHEN_STARTED) {
|
||||
// The first stream change hasn't been processed by the VideoGraph yet.
|
||||
streamChangeInfo =
|
||||
new StreamChangeInfo(
|
||||
streamChangeInfo.startPositionUs,
|
||||
RELEASE_FIRST_FRAME_IMMEDIATELY,
|
||||
streamChangeInfo.fromTimestampUs);
|
||||
} else {
|
||||
// The first stream change has already been processed by the VideoGraph. Delegate to the
|
||||
// downstream component.
|
||||
defaultVideoSink.allowReleaseFirstFrameBeforeStarted();
|
||||
}
|
||||
isFirstStreamChange = false;
|
||||
}
|
||||
newPendingStreamChanges.add(streamChangeInfo.fromTimestampUs, streamChangeInfo);
|
||||
}
|
||||
pendingStreamChanges = newPendingStreamChanges;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -883,13 +914,6 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
defaultVideoSink.setChangeFrameRateStrategy(changeFrameRateStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableMayRenderStartOfStream() {
|
||||
if (nextFirstOutputFrameReleaseInstruction == RELEASE_FIRST_FRAME_WHEN_STARTED) {
|
||||
nextFirstOutputFrameReleaseInstruction = RELEASE_FIRST_FRAME_IMMEDIATELY;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleInputFrame(
|
||||
long framePresentationTimeUs, boolean isLastFrame, VideoFrameHandler videoFrameHandler) {
|
||||
@ -1060,6 +1084,21 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StreamChangeInfo {
|
||||
public final long startPositionUs;
|
||||
public final @VideoSink.FirstFrameReleaseInstruction int firstFrameReleaseInstruction;
|
||||
public final long fromTimestampUs;
|
||||
|
||||
public StreamChangeInfo(
|
||||
long startPositionUs,
|
||||
@VideoSink.FirstFrameReleaseInstruction int firstFrameReleaseInstruction,
|
||||
long fromTimestampUs) {
|
||||
this.startPositionUs = startPositionUs;
|
||||
this.firstFrameReleaseInstruction = firstFrameReleaseInstruction;
|
||||
this.fromTimestampUs = fromTimestampUs;
|
||||
}
|
||||
}
|
||||
|
||||
/** Delays reflection for loading a {@link VideoGraph.Factory SingleInputVideoGraph} instance. */
|
||||
private static final class ReflectiveSingleInputVideoGraphFactory implements VideoGraph.Factory {
|
||||
|
||||
|
@ -154,9 +154,6 @@ public interface VideoSink {
|
||||
*/
|
||||
int RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED = 2;
|
||||
|
||||
/** Called when the {@link Renderer} currently feeding this sink is enabled. */
|
||||
void onRendererEnabled(boolean mayRenderStartOfStream);
|
||||
|
||||
/** Called when the {@link Renderer} currently feeding this sink is started. */
|
||||
void onRendererStarted();
|
||||
|
||||
@ -261,15 +258,6 @@ public interface VideoSink {
|
||||
*/
|
||||
void setChangeFrameRateStrategy(@C.VideoChangeFrameRateStrategy int changeFrameRateStrategy);
|
||||
|
||||
/**
|
||||
* Enables this video sink to render the start of the stream to its output surface even if the
|
||||
* renderer is not {@linkplain #onRendererStarted() started} yet.
|
||||
*
|
||||
* <p>This is used to update the value of {@code mayRenderStartOfStream} passed to {@link
|
||||
* #onRendererEnabled(boolean)}.
|
||||
*/
|
||||
void enableMayRenderStartOfStream();
|
||||
|
||||
/**
|
||||
* Informs the video sink that a new input stream will be queued with the given effects.
|
||||
*
|
||||
@ -290,6 +278,15 @@ public interface VideoSink {
|
||||
@FirstFrameReleaseInstruction int firstFrameReleaseInstruction,
|
||||
List<Effect> videoEffects);
|
||||
|
||||
/**
|
||||
* Allows the sink to release the first frame even if rendering is not {@linkplain
|
||||
* #onRendererStarted() started}.
|
||||
*
|
||||
* <p>This is used to update the {@link FirstFrameReleaseInstruction} of the {@linkplain
|
||||
* #onInputStreamChanged(int, Format, long, int, List) stream} that is currently being processed.
|
||||
*/
|
||||
void allowReleaseFirstFrameBeforeStarted();
|
||||
|
||||
/**
|
||||
* Handles a video input frame.
|
||||
*
|
||||
|
@ -81,11 +81,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
pendingOperations.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRendererEnabled(boolean mayRenderStartOfStream) {
|
||||
executeOrDelay(videoSink -> videoSink.onRendererEnabled(mayRenderStartOfStream));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRendererStarted() {
|
||||
executeOrDelay(VideoSink::onRendererStarted);
|
||||
@ -213,11 +208,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
executeOrDelay(videoSink -> videoSink.setChangeFrameRateStrategy(changeFrameRateStrategy));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableMayRenderStartOfStream() {
|
||||
executeOrDelay(VideoSink::enableMayRenderStartOfStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputStreamChanged(
|
||||
@InputType int inputType,
|
||||
@ -231,6 +221,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
inputType, format, startPositionUs, firstFrameReleaseInstruction, videoEffects));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allowReleaseFirstFrameBeforeStarted() {
|
||||
executeOrDelay(VideoSink::allowReleaseFirstFrameBeforeStarted);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
@ -24,7 +24,9 @@ import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
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.MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY;
|
||||
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 android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
@ -435,12 +437,15 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
@Override
|
||||
protected void changeVideoSinkInputStream(
|
||||
VideoSink videoSink, @VideoSink.InputType int inputType, Format format) {
|
||||
VideoSink videoSink,
|
||||
@VideoSink.InputType int inputType,
|
||||
Format format,
|
||||
@VideoSink.FirstFrameReleaseInstruction int firstFrameReleaseInstruction) {
|
||||
videoSink.onInputStreamChanged(
|
||||
inputType,
|
||||
format,
|
||||
getOutputStreamStartPositionUs(),
|
||||
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED,
|
||||
firstFrameReleaseInstruction,
|
||||
pendingEffects);
|
||||
}
|
||||
|
||||
@ -493,6 +498,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private boolean inputStreamPending;
|
||||
private long streamStartPositionUs;
|
||||
private boolean mayRenderStartOfStream;
|
||||
private @VideoSink.FirstFrameReleaseInstruction int nextFirstFrameReleaseInstruction;
|
||||
private long offsetToCompositionTimeUs;
|
||||
|
||||
public SequenceImageRenderer(
|
||||
@ -513,7 +519,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
throws ExoPlaybackException {
|
||||
super.onEnabled(joining, mayRenderStartOfStream);
|
||||
this.mayRenderStartOfStream = mayRenderStartOfStream;
|
||||
videoSink.onRendererEnabled(mayRenderStartOfStream);
|
||||
nextFirstFrameReleaseInstruction =
|
||||
mayRenderStartOfStream
|
||||
? RELEASE_FIRST_FRAME_IMMEDIATELY
|
||||
: RELEASE_FIRST_FRAME_WHEN_STARTED;
|
||||
// TODO: b/328444280 - Do not set a listener on VideoSink, but MediaCodecVideoRenderer must
|
||||
// unregister itself as a listener too.
|
||||
videoSink.setListener(VideoSink.Listener.NO_OP, /* executor= */ (runnable) -> {});
|
||||
@ -630,8 +639,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
.setFrameRate(/* frameRate= */ DEFAULT_FRAME_RATE)
|
||||
.build(),
|
||||
streamStartPositionUs,
|
||||
RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED,
|
||||
nextFirstFrameReleaseInstruction,
|
||||
videoEffects);
|
||||
nextFirstFrameReleaseInstruction = RELEASE_FIRST_FRAME_WHEN_PREVIOUS_STREAM_PROCESSED;
|
||||
inputStreamPending = false;
|
||||
}
|
||||
if (!videoSink.handleInputBitmap(outputImage, checkStateNotNull(timestampIterator))) {
|
||||
|
@ -40,26 +40,25 @@ public class BufferingVideoSinkTest {
|
||||
VideoSink videoSinkMock = mock(VideoSink.class);
|
||||
|
||||
bufferingVideoSink.setVideoSink(videoSinkMock);
|
||||
bufferingVideoSink.onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
||||
bufferingVideoSink.onRendererStarted();
|
||||
bufferingVideoSink.flush(/* resetPosition= */ true);
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(videoSinkMock);
|
||||
inOrder.verify(videoSinkMock).onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
||||
inOrder.verify(videoSinkMock).onRendererStarted();
|
||||
inOrder.verify(videoSinkMock).flush(/* resetPosition= */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setVideoSink_executesPendingOperations() {
|
||||
BufferingVideoSink bufferingVideoSink = new BufferingVideoSink(context);
|
||||
VideoSink videoSinkMock = mock(VideoSink.class);
|
||||
|
||||
bufferingVideoSink.onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
||||
bufferingVideoSink.onRendererStarted();
|
||||
bufferingVideoSink.flush(/* resetPosition= */ true);
|
||||
bufferingVideoSink.setVideoSink(videoSinkMock);
|
||||
|
||||
InOrder inOrder = Mockito.inOrder(videoSinkMock);
|
||||
inOrder.verify(videoSinkMock).onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
||||
inOrder.verify(videoSinkMock).onRendererStarted();
|
||||
inOrder.verify(videoSinkMock).flush(/* resetPosition= */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -69,11 +68,11 @@ public class BufferingVideoSinkTest {
|
||||
bufferingVideoSink.setVideoSink(videoSinkMock);
|
||||
|
||||
bufferingVideoSink.setVideoSink(null);
|
||||
bufferingVideoSink.onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
||||
bufferingVideoSink.onRendererStarted();
|
||||
bufferingVideoSink.flush(/* resetPosition= */ true);
|
||||
|
||||
verify(videoSinkMock, never()).onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
||||
verify(videoSinkMock, never()).onRendererStarted();
|
||||
verify(videoSinkMock, never()).flush(/* resetPosition= */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -81,12 +80,12 @@ public class BufferingVideoSinkTest {
|
||||
BufferingVideoSink bufferingVideoSink = new BufferingVideoSink(context);
|
||||
VideoSink videoSinkMock = mock(VideoSink.class);
|
||||
|
||||
bufferingVideoSink.onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
||||
bufferingVideoSink.onRendererStarted();
|
||||
bufferingVideoSink.flush(/* resetPosition= */ true);
|
||||
bufferingVideoSink.clearPendingOperations();
|
||||
bufferingVideoSink.setVideoSink(videoSinkMock);
|
||||
|
||||
verify(videoSinkMock, never()).onRendererEnabled(/* mayRenderStartOfStream= */ true);
|
||||
verify(videoSinkMock, never()).onRendererStarted();
|
||||
verify(videoSinkMock, never()).flush(/* resetPosition= */ true);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user