mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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
This commit is contained in:
parent
dc7a0ca22f
commit
3c01500a4e
@ -836,7 +836,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
public void handleMessage(@MessageType int messageType, @Nullable Object message)
|
public void handleMessage(@MessageType int messageType, @Nullable Object message)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
if (messageType == MSG_SET_WAKEUP_LISTENER) {
|
if (messageType == MSG_SET_WAKEUP_LISTENER) {
|
||||||
this.wakeupListener = (WakeupListener) message;
|
wakeupListener = (WakeupListener) message;
|
||||||
|
onWakeupListenerSet(checkNotNull(wakeupListener));
|
||||||
} else {
|
} else {
|
||||||
super.handleMessage(messageType, message);
|
super.handleMessage(messageType, message);
|
||||||
}
|
}
|
||||||
@ -1542,6 +1543,17 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a {@link WakeupListener} is set.
|
||||||
|
*
|
||||||
|
* <p>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}.
|
* Called when a new {@link Format} is read from the upstream {@link MediaPeriod}.
|
||||||
*
|
*
|
||||||
|
@ -26,6 +26,7 @@ import androidx.media3.common.Format;
|
|||||||
import androidx.media3.common.util.Size;
|
import androidx.media3.common.util.Size;
|
||||||
import androidx.media3.common.util.TimestampIterator;
|
import androidx.media3.common.util.TimestampIterator;
|
||||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||||
|
import androidx.media3.exoplayer.Renderer;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ import java.util.concurrent.Executor;
|
|||||||
* <ul>
|
* <ul>
|
||||||
* <li>Applying video effects
|
* <li>Applying video effects
|
||||||
* <li>Inputting bitmaps
|
* <li>Inputting bitmaps
|
||||||
|
* <li>Setting WakeupListener
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>The {@linkplain #getInputSurface() input} and {@linkplain #setOutputSurfaceInfo(Surface, Size)
|
* <p>The {@linkplain #getInputSurface() input} and {@linkplain #setOutputSurfaceInfo(Surface, Size)
|
||||||
@ -220,6 +222,16 @@ import java.util.concurrent.Executor;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>This method will always throw an {@link UnsupportedOperationException}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setWakeupListener(Renderer.WakeupListener wakeupListener) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void join(boolean renderNextFrameImmediately) {
|
public void join(boolean renderNextFrameImmediately) {
|
||||||
videoFrameReleaseControl.join(renderNextFrameImmediately);
|
videoFrameReleaseControl.join(renderNextFrameImmediately);
|
||||||
|
@ -757,6 +757,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
videoSink.setVideoEffects(videoEffects);
|
videoSink.setVideoEffects(videoEffects);
|
||||||
}
|
}
|
||||||
videoSink.onRendererEnabled(mayRenderStartOfStream);
|
videoSink.onRendererEnabled(mayRenderStartOfStream);
|
||||||
|
@Nullable WakeupListener wakeupListener = getWakeupListener();
|
||||||
|
if (wakeupListener != null) {
|
||||||
|
videoSink.setWakeupListener(wakeupListener);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
videoFrameReleaseControl.setClock(getClock());
|
videoFrameReleaseControl.setClock(getClock());
|
||||||
videoFrameReleaseControl.onEnabled(mayRenderStartOfStream);
|
videoFrameReleaseControl.onEnabled(mayRenderStartOfStream);
|
||||||
@ -1250,6 +1254,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
|
|||||||
eventDispatcher.videoCodecError(codecError);
|
eventDispatcher.videoCodecError(codecError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onWakeupListenerSet(WakeupListener wakeupListener) {
|
||||||
|
if (videoSink != null) {
|
||||||
|
videoSink.setWakeupListener(wakeupListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
|
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
|
||||||
|
@ -51,6 +51,7 @@ import androidx.media3.common.util.TimestampIterator;
|
|||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.exoplayer.ExoPlaybackException;
|
import androidx.media3.exoplayer.ExoPlaybackException;
|
||||||
|
import androidx.media3.exoplayer.Renderer;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.base.Suppliers;
|
import com.google.common.base.Suppliers;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
@ -252,6 +253,7 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
@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;
|
||||||
|
@Nullable private Renderer.WakeupListener wakeupListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the buffer timestamp (the player position, with renderer offset) to the composition
|
* 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
|
// Ignore available frames while flushing
|
||||||
return;
|
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
|
// The frame presentation time is relative to the start of the Composition and without the
|
||||||
// renderer offset
|
// renderer offset
|
||||||
long bufferPresentationTimeUs = framePresentationTimeUs - bufferTimestampAdjustmentUs;
|
long bufferPresentationTimeUs = framePresentationTimeUs - bufferTimestampAdjustmentUs;
|
||||||
@ -829,6 +835,11 @@ public final class PlaybackVideoGraphWrapper implements VideoSinkProvider, Video
|
|||||||
PlaybackVideoGraphWrapper.this.render(positionUs, elapsedRealtimeUs);
|
PlaybackVideoGraphWrapper.this.render(positionUs, elapsedRealtimeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWakeupListener(Renderer.WakeupListener wakeupListener) {
|
||||||
|
PlaybackVideoGraphWrapper.this.wakeupListener = wakeupListener;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void join(boolean renderNextFrameImmediately) {
|
public void join(boolean renderNextFrameImmediately) {
|
||||||
defaultVideoSink.join(renderNextFrameImmediately);
|
defaultVideoSink.join(renderNextFrameImmediately);
|
||||||
|
@ -294,6 +294,9 @@ public interface VideoSink {
|
|||||||
*/
|
*/
|
||||||
void render(long positionUs, long elapsedRealtimeUs) throws VideoSinkException;
|
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.
|
* Joins the video sink to a new stream.
|
||||||
*
|
*
|
||||||
|
@ -23,6 +23,7 @@ import androidx.media3.common.Effect;
|
|||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.util.Size;
|
import androidx.media3.common.util.Size;
|
||||||
import androidx.media3.common.util.TimestampIterator;
|
import androidx.media3.common.util.TimestampIterator;
|
||||||
|
import androidx.media3.exoplayer.Renderer;
|
||||||
import androidx.media3.exoplayer.video.PlaceholderSurface;
|
import androidx.media3.exoplayer.video.PlaceholderSurface;
|
||||||
import androidx.media3.exoplayer.video.VideoFrameMetadataListener;
|
import androidx.media3.exoplayer.video.VideoFrameMetadataListener;
|
||||||
import androidx.media3.exoplayer.video.VideoSink;
|
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
|
@Override
|
||||||
public void join(boolean renderNextFrameImmediately) {
|
public void join(boolean renderNextFrameImmediately) {
|
||||||
executeOrDelay(videoSink -> videoSink.join(renderNextFrameImmediately));
|
executeOrDelay(videoSink -> videoSink.join(renderNextFrameImmediately));
|
||||||
|
@ -700,7 +700,10 @@ public final class CompositionPlayer extends SimpleBasePlayer
|
|||||||
.setPlaybackLooper(playbackThread.getLooper())
|
.setPlaybackLooper(playbackThread.getLooper())
|
||||||
.setRenderersFactory(sequenceRenderersFactory)
|
.setRenderersFactory(sequenceRenderersFactory)
|
||||||
.setHandleAudioBecomingNoisy(true)
|
.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;
|
boolean disableVideoPlayback = false;
|
||||||
for (int j = 0; j < editedMediaItemSequence.editedMediaItems.size(); j++) {
|
for (int j = 0; j < editedMediaItemSequence.editedMediaItems.size(); j++) {
|
||||||
|
@ -476,6 +476,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
return videoSink.handleInputBitmap(outputImage, checkStateNotNull(timestampIterator));
|
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) {
|
private ConstantRateTimestampIterator createTimestampIterator(long positionUs) {
|
||||||
long streamOffsetUs = getStreamOffsetUs();
|
long streamOffsetUs = getStreamOffsetUs();
|
||||||
long imageBaseTimestampUs = streamOffsetUs + offsetToCompositionTimeUs;
|
long imageBaseTimestampUs = streamOffsetUs + offsetToCompositionTimeUs;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user