From 3c01500a4e6a7105a2f1c1439ceb451c035a9fe7 Mon Sep 17 00:00:00 2001 From: claincly Date: Tue, 19 Nov 2024 10:30:26 -0800 Subject: [PATCH] Use dynamic scheduling to speed up rendering the first image frame 3P app reported it's slow to see the first image frame on the screen, when `playWhenReady` is false. This is because the player would wake up less frequently when `playWhenReady` is false. This CL adds a runnable to wake up the player any time a new frame is made available. PiperOrigin-RevId: 698066887 --- .../exoplayer/mediacodec/MediaCodecRenderer.java | 14 +++++++++++++- .../media3/exoplayer/video/DefaultVideoSink.java | 12 ++++++++++++ .../exoplayer/video/MediaCodecVideoRenderer.java | 11 +++++++++++ .../exoplayer/video/PlaybackVideoGraphWrapper.java | 11 +++++++++++ .../androidx/media3/exoplayer/video/VideoSink.java | 3 +++ .../media3/transformer/BufferingVideoSink.java | 6 ++++++ .../media3/transformer/CompositionPlayer.java | 5 ++++- .../transformer/SequenceRenderersFactory.java | 12 ++++++++++++ 8 files changed, 72 insertions(+), 2 deletions(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java index 64887aecc3..34ca459482 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java @@ -836,7 +836,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { public void handleMessage(@MessageType int messageType, @Nullable Object message) throws ExoPlaybackException { if (messageType == MSG_SET_WAKEUP_LISTENER) { - this.wakeupListener = (WakeupListener) message; + wakeupListener = (WakeupListener) message; + onWakeupListenerSet(checkNotNull(wakeupListener)); } else { super.handleMessage(messageType, message); } @@ -1542,6 +1543,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer { // Do nothing. } + /** + * Called when a {@link WakeupListener} is set. + * + *

The default implementation is a no-op. + * + * @param wakeupListener The {@link WakeupListener}. + */ + protected void onWakeupListenerSet(WakeupListener wakeupListener) { + // Do nothing. + } + /** * Called when a new {@link Format} is read from the upstream {@link MediaPeriod}. * 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 7d532c1092..64ab313d1e 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 @@ -26,6 +26,7 @@ import androidx.media3.common.Format; import androidx.media3.common.util.Size; import androidx.media3.common.util.TimestampIterator; import androidx.media3.exoplayer.ExoPlaybackException; +import androidx.media3.exoplayer.Renderer; import java.util.List; import java.util.concurrent.Executor; @@ -38,6 +39,7 @@ import java.util.concurrent.Executor; *

* *

The {@linkplain #getInputSurface() input} and {@linkplain #setOutputSurfaceInfo(Surface, Size) @@ -220,6 +222,16 @@ import java.util.concurrent.Executor; } } + /** + * {@inheritDoc} + * + *

This method will always throw an {@link UnsupportedOperationException}. + */ + @Override + public void setWakeupListener(Renderer.WakeupListener wakeupListener) { + throw new UnsupportedOperationException(); + } + @Override public void join(boolean renderNextFrameImmediately) { videoFrameReleaseControl.join(renderNextFrameImmediately); 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 53d0f42468..6a604553f6 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 @@ -757,6 +757,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer videoSink.setVideoEffects(videoEffects); } videoSink.onRendererEnabled(mayRenderStartOfStream); + @Nullable WakeupListener wakeupListener = getWakeupListener(); + if (wakeupListener != null) { + videoSink.setWakeupListener(wakeupListener); + } } else { videoFrameReleaseControl.setClock(getClock()); videoFrameReleaseControl.onEnabled(mayRenderStartOfStream); @@ -1250,6 +1254,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer eventDispatcher.videoCodecError(codecError); } + @Override + protected void onWakeupListenerSet(WakeupListener wakeupListener) { + if (videoSink != null) { + videoSink.setWakeupListener(wakeupListener); + } + } + @Override @Nullable protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/PlaybackVideoGraphWrapper.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/PlaybackVideoGraphWrapper.java index f3510c3836..55c7834893 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/PlaybackVideoGraphWrapper.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/PlaybackVideoGraphWrapper.java @@ -51,6 +51,7 @@ import androidx.media3.common.util.TimestampIterator; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import androidx.media3.exoplayer.ExoPlaybackException; +import androidx.media3.exoplayer.Renderer; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; @@ -252,6 +253,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video @Nullable private Pair currentSurfaceAndSize; private int pendingFlushCount; private @State int state; + @Nullable private Renderer.WakeupListener wakeupListener; /** * Converts the buffer timestamp (the player position, with renderer offset) to the composition @@ -363,6 +365,10 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video // Ignore available frames while flushing return; } + if (wakeupListener != null) { + // Wake up the player when not playing to render the frame more promptly. + wakeupListener.onWakeup(); + } // The frame presentation time is relative to the start of the Composition and without the // renderer offset long bufferPresentationTimeUs = framePresentationTimeUs - bufferTimestampAdjustmentUs; @@ -829,6 +835,11 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video PlaybackVideoGraphWrapper.this.render(positionUs, elapsedRealtimeUs); } + @Override + public void setWakeupListener(Renderer.WakeupListener wakeupListener) { + PlaybackVideoGraphWrapper.this.wakeupListener = wakeupListener; + } + @Override public void join(boolean renderNextFrameImmediately) { defaultVideoSink.join(renderNextFrameImmediately); diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoSink.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoSink.java index 54e237d9b8..4069c38b23 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoSink.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/VideoSink.java @@ -294,6 +294,9 @@ public interface VideoSink { */ void render(long positionUs, long elapsedRealtimeUs) throws VideoSinkException; + /** Sets a {@link Renderer.WakeupListener} on the {@code VideoSink}. */ + void setWakeupListener(Renderer.WakeupListener wakeupListener); + /** * Joins the video sink to a new stream. * diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/BufferingVideoSink.java b/libraries/transformer/src/main/java/androidx/media3/transformer/BufferingVideoSink.java index 678317816c..7df14902b2 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/BufferingVideoSink.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/BufferingVideoSink.java @@ -23,6 +23,7 @@ import androidx.media3.common.Effect; import androidx.media3.common.Format; import androidx.media3.common.util.Size; import androidx.media3.common.util.TimestampIterator; +import androidx.media3.exoplayer.Renderer; import androidx.media3.exoplayer.video.PlaceholderSurface; import androidx.media3.exoplayer.video.VideoFrameMetadataListener; import androidx.media3.exoplayer.video.VideoSink; @@ -229,6 +230,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } } + @Override + public void setWakeupListener(Renderer.WakeupListener wakeupListener) { + executeOrDelay(videoSink -> videoSink.setWakeupListener(wakeupListener)); + } + @Override public void join(boolean renderNextFrameImmediately) { executeOrDelay(videoSink -> videoSink.join(renderNextFrameImmediately)); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayer.java index 126cf8f99c..626a11056a 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/CompositionPlayer.java @@ -700,7 +700,10 @@ public final class CompositionPlayer extends SimpleBasePlayer .setPlaybackLooper(playbackThread.getLooper()) .setRenderersFactory(sequenceRenderersFactory) .setHandleAudioBecomingNoisy(true) - .setClock(clock); + .setClock(clock) + // Use dynamic scheduling to show the first video/image frame more promptly when the + // player is paused (which is common in editing applications). + .experimentalSetDynamicSchedulingEnabled(true); boolean disableVideoPlayback = false; for (int j = 0; j < editedMediaItemSequence.editedMediaItems.size(); j++) { diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/SequenceRenderersFactory.java b/libraries/transformer/src/main/java/androidx/media3/transformer/SequenceRenderersFactory.java index a3ccd8d3d0..34bc035f79 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/SequenceRenderersFactory.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/SequenceRenderersFactory.java @@ -476,6 +476,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; return videoSink.handleInputBitmap(outputImage, checkStateNotNull(timestampIterator)); } + @Override + public void handleMessage(@MessageType int messageType, @Nullable Object message) + throws ExoPlaybackException { + switch (messageType) { + case MSG_SET_WAKEUP_LISTENER: + videoSink.setWakeupListener((WakeupListener) checkNotNull(message)); + break; + default: + super.handleMessage(messageType, message); + } + } + private ConstantRateTimestampIterator createTimestampIterator(long positionUs) { long streamOffsetUs = getStreamOffsetUs(); long imageBaseTimestampUs = streamOffsetUs + offsetToCompositionTimeUs;