Commit 3032252f by olly Committed by marcbaechinger

Use a single message for setting video renderer outputs

Previously, we had separate MSG_SET_SURFACE and
MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER messages for
setting different types of supported output. Use of these
constants to switch between outputs during use of a player
was confusing because not all video renderers support both
message types.

To switch from VideoDecoderOutputBufferRenderer to a Surface,
it was sufficient just to send MSG_SET_SURFACE, since all
video renderers support this and clear any other output that
might be set. Conversely, to switch in the opposite direction,
just sending a MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER was
not sufficient, because not all video renderers handle this
message to clear any previous output. Hence it was necessary to
explicitly clear a previously set surface using a separate
MSG_SET_SURFACE message. Passing two messages to switch the
output may prevent renderers from implementing the output switch
efficiently.

This change passes all outputs using a single message type, and
requires that all renderers treat unsupported outputs as though
null were passed (i.e., they clear any existing output). There
are some other miscellaneous improvements:

1. Non-surface outputs are now passed to onRenderedFirstFrame.
   This fixes a bug in SimpleExoPlayer's onRenderedFirstFrame,
   where previously it could not correctly equality check the
   output corresponding to the event to its current output in
   the VideoDecoderOutputBufferRenderer case.
2. Fix SimpleExoPlayer to report surface size changes for the
   VideoDecoderOutputBufferRenderer case. Even though the
   surface is rendered to indirectly in this case, we can still
   query (and listen to changes to) the surface's size.

PiperOrigin-RevId: 368215850
parent 84282d7c
...@@ -782,7 +782,7 @@ public final class C { ...@@ -782,7 +782,7 @@ public final class C {
*/ */
public static final UUID PLAYREADY_UUID = new UUID(0x9A04F07998404286L, 0xAB92E65BE0885F95L); public static final UUID PLAYREADY_UUID = new UUID(0x9A04F07998404286L, 0xAB92E65BE0885F95L);
/** @deprecated Use {@code Renderer.MSG_SET_SURFACE}. */ /** @deprecated Use {@code Renderer.MSG_SET_VIDEO_OUTPUT}. */
@Deprecated public static final int MSG_SET_SURFACE = 1; @Deprecated public static final int MSG_SET_SURFACE = 1;
/** @deprecated Use {@code Renderer.MSG_SET_VOLUME}. */ /** @deprecated Use {@code Renderer.MSG_SET_VOLUME}. */
...@@ -803,9 +803,6 @@ public final class C { ...@@ -803,9 +803,6 @@ public final class C {
/** @deprecated Use {@code Renderer.MSG_SET_CAMERA_MOTION_LISTENER}. */ /** @deprecated Use {@code Renderer.MSG_SET_CAMERA_MOTION_LISTENER}. */
@Deprecated public static final int MSG_SET_CAMERA_MOTION_LISTENER = 7; @Deprecated public static final int MSG_SET_CAMERA_MOTION_LISTENER = 7;
/** @deprecated Use {@code Renderer.MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER}. */
@Deprecated public static final int MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER = 8;
/** @deprecated Use {@code Renderer.MSG_CUSTOM_BASE}. */ /** @deprecated Use {@code Renderer.MSG_CUSTOM_BASE}. */
@Deprecated public static final int MSG_CUSTOM_BASE = 10000; @Deprecated public static final int MSG_CUSTOM_BASE = 10000;
......
...@@ -196,7 +196,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -196,7 +196,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
// PlayerMessage.Target implementation. // PlayerMessage.Target implementation.
@Override @Override
public void handleMessage(int what, @Nullable Object object) throws ExoPlaybackException { public void handleMessage(int messageType, @Nullable Object payload) throws ExoPlaybackException {
// Do nothing. // Do nothing.
} }
......
...@@ -24,7 +24,6 @@ import com.google.android.exoplayer2.audio.AuxEffectInfo; ...@@ -24,7 +24,6 @@ import com.google.android.exoplayer2.audio.AuxEffectInfo;
import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.util.MediaClock; import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.DecoderVideoRenderer;
import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer; import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer;
import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
import com.google.android.exoplayer2.video.spherical.CameraMotionListener; import com.google.android.exoplayer2.video.spherical.CameraMotionListener;
...@@ -76,11 +75,14 @@ public interface Renderer extends PlayerMessage.Target { ...@@ -76,11 +75,14 @@ public interface Renderer extends PlayerMessage.Target {
/** /**
* The type of a message that can be passed to a video renderer via {@link * The type of a message that can be passed to a video renderer via {@link
* ExoPlayer#createMessage(Target)}. The message payload should be the target {@link Surface}, or * ExoPlayer#createMessage(Target)}. The message payload is normally a {@link Surface}, however
* null. * some video renderers may accept other outputs (e.g., {@link VideoDecoderOutputBufferRenderer}).
*
* <p>If the receiving renderer does not support the payload type as an output, then it will clear
* any existing output that it has.
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
int MSG_SET_SURFACE = C.MSG_SET_SURFACE; int MSG_SET_VIDEO_OUTPUT = C.MSG_SET_SURFACE;
/** /**
* A type of a message that can be passed to an audio renderer via {@link * A type of a message that can be passed to an audio renderer via {@link
* ExoPlayer#createMessage(Target)}. The message payload should be a {@link Float} with 0 being * ExoPlayer#createMessage(Target)}. The message payload should be a {@link Float} with 0 being
...@@ -143,17 +145,6 @@ public interface Renderer extends PlayerMessage.Target { ...@@ -143,17 +145,6 @@ public interface Renderer extends PlayerMessage.Target {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
int MSG_SET_CAMERA_MOTION_LISTENER = C.MSG_SET_CAMERA_MOTION_LISTENER; int MSG_SET_CAMERA_MOTION_LISTENER = C.MSG_SET_CAMERA_MOTION_LISTENER;
/** /**
* The type of a message that can be passed to a {@link DecoderVideoRenderer} via {@link
* ExoPlayer#createMessage(Target)}. The message payload should be the target {@link
* VideoDecoderOutputBufferRenderer}, or null.
*
* <p>This message is intended only for use with extension renderers that expect a {@link
* VideoDecoderOutputBufferRenderer}. For other use cases, an output surface should be passed via
* {@link #MSG_SET_SURFACE} instead.
*/
@SuppressWarnings("deprecation")
int MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER = C.MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER;
/**
* The type of a message that can be passed to an audio renderer via {@link * The type of a message that can be passed to an audio renderer via {@link
* ExoPlayer#createMessage(Target)}. The message payload should be a {@link Boolean} instance * ExoPlayer#createMessage(Target)}. The message payload should be a {@link Boolean} instance
* telling whether to enable or disable skipping silences in the audio stream. * telling whether to enable or disable skipping silences in the audio stream.
......
...@@ -19,7 +19,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull; ...@@ -19,7 +19,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.os.Looper; import android.os.Looper;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.Surface;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -442,17 +441,13 @@ public class AnalyticsCollector ...@@ -442,17 +441,13 @@ public class AnalyticsCollector
eventTime, width, height, unappliedRotationDegrees, pixelWidthHeightRatio)); eventTime, width, height, unappliedRotationDegrees, pixelWidthHeightRatio));
} }
@SuppressWarnings("deprecation") // Calling deprecated listener method.
@Override @Override
public final void onRenderedFirstFrame(@Nullable Surface surface, long renderTimeMs) { public final void onRenderedFirstFrame(Object output, long renderTimeMs) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
sendEvent( sendEvent(
eventTime, eventTime,
AnalyticsListener.EVENT_RENDERED_FIRST_FRAME, AnalyticsListener.EVENT_RENDERED_FIRST_FRAME,
listener -> { listener -> listener.onRenderedFirstFrame(eventTime, output, renderTimeMs));
listener.onRenderedFirstFrame(eventTime, surface);
listener.onRenderedFirstFrame(eventTime, surface, renderTimeMs);
});
} }
@Override @Override
......
...@@ -48,6 +48,7 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; ...@@ -48,6 +48,7 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.ExoFlags; import com.google.android.exoplayer2.util.ExoFlags;
import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
...@@ -1020,16 +1021,11 @@ public interface AnalyticsListener { ...@@ -1020,16 +1021,11 @@ public interface AnalyticsListener {
* renderer was reset, or since the stream being rendered was changed. * renderer was reset, or since the stream being rendered was changed.
* *
* @param eventTime The event time. * @param eventTime The event time.
* @param surface The {@link Surface} to which a frame has been rendered, or {@code null} if the * @param output The output to which a frame has been rendered. Normally a {@link Surface},
* renderer renders to something that isn't a {@link Surface}. * however may also be other output types (e.g., a {@link VideoDecoderOutputBufferRenderer}).
* @param renderTimeMs {@link SystemClock#elapsedRealtime()} when the first frame was rendered. * @param renderTimeMs {@link SystemClock#elapsedRealtime()} when the first frame was rendered.
*/ */
default void onRenderedFirstFrame( default void onRenderedFirstFrame(EventTime eventTime, Object output, long renderTimeMs) {}
EventTime eventTime, @Nullable Surface surface, long renderTimeMs) {}
/** @deprecated Use {@link #onRenderedFirstFrame(EventTime, Surface, long)} instead. */
@Deprecated
default void onRenderedFirstFrame(EventTime eventTime, @Nullable Surface surface) {}
/** /**
* Called before a frame is rendered for the first time since setting the surface, and each time * Called before a frame is rendered for the first time since setting the surface, and each time
......
...@@ -19,7 +19,6 @@ import static java.lang.Math.min; ...@@ -19,7 +19,6 @@ import static java.lang.Math.min;
import android.os.SystemClock; import android.os.SystemClock;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.Surface;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
...@@ -453,8 +452,8 @@ public class EventLogger implements AnalyticsListener { ...@@ -453,8 +452,8 @@ public class EventLogger implements AnalyticsListener {
} }
@Override @Override
public void onRenderedFirstFrame(EventTime eventTime, @Nullable Surface surface) { public void onRenderedFirstFrame(EventTime eventTime, Object output, long renderTimeMs) {
logd(eventTime, "renderedFirstFrame", String.valueOf(surface)); logd(eventTime, "renderedFirstFrame", String.valueOf(output));
} }
@Override @Override
......
...@@ -29,6 +29,7 @@ import androidx.annotation.IntDef; ...@@ -29,6 +29,7 @@ import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.BaseRenderer;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.C.VideoOutputMode;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
...@@ -59,11 +60,9 @@ import java.lang.annotation.RetentionPolicy; ...@@ -59,11 +60,9 @@ import java.lang.annotation.RetentionPolicy;
* on the playback thread: * on the playback thread:
* *
* <ul> * <ul>
* <li>Message with type {@link #MSG_SET_SURFACE} to set the output surface. The message payload * <li>Message with type {@link #MSG_SET_VIDEO_OUTPUT} to set the output surface. The message
* should be the target {@link Surface}, or null. * payload should be the target {@link Surface} or {@link VideoDecoderOutputBufferRenderer},
* <li>Message with type {@link #MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER} to set the output * or null. Other non-null payloads have the effect of clearing the output.
* buffer renderer. The message payload should be the target {@link
* VideoDecoderOutputBufferRenderer}, or null.
* <li>Message with type {@link #MSG_SET_VIDEO_FRAME_METADATA_LISTENER} to set a listener for * <li>Message with type {@link #MSG_SET_VIDEO_FRAME_METADATA_LISTENER} to set a listener for
* metadata associated with frames being rendered. The message payload should be the {@link * metadata associated with frames being rendered. The message payload should be the {@link
* VideoFrameMetadataListener}, or null. * VideoFrameMetadataListener}, or null.
...@@ -113,10 +112,11 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { ...@@ -113,10 +112,11 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
private VideoDecoderInputBuffer inputBuffer; private VideoDecoderInputBuffer inputBuffer;
private VideoDecoderOutputBuffer outputBuffer; private VideoDecoderOutputBuffer outputBuffer;
@Nullable private Surface surface; @VideoOutputMode private int outputMode;
@Nullable private Object output;
@Nullable private Surface outputSurface;
@Nullable private VideoDecoderOutputBufferRenderer outputBufferRenderer; @Nullable private VideoDecoderOutputBufferRenderer outputBufferRenderer;
@Nullable private VideoFrameMetadataListener frameMetadataListener; @Nullable private VideoFrameMetadataListener frameMetadataListener;
@C.VideoOutputMode private int outputMode;
@Nullable private DrmSession decoderDrmSession; @Nullable private DrmSession decoderDrmSession;
@Nullable private DrmSession sourceDrmSession; @Nullable private DrmSession sourceDrmSession;
...@@ -248,10 +248,8 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { ...@@ -248,10 +248,8 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
@Override @Override
public void handleMessage(int messageType, @Nullable Object message) throws ExoPlaybackException { public void handleMessage(int messageType, @Nullable Object message) throws ExoPlaybackException {
if (messageType == MSG_SET_SURFACE) { if (messageType == MSG_SET_VIDEO_OUTPUT) {
setOutputSurface((Surface) message); setOutput(message);
} else if (messageType == MSG_SET_VIDEO_DECODER_OUTPUT_BUFFER_RENDERER) {
setOutputBufferRenderer((VideoDecoderOutputBufferRenderer) message);
} else if (messageType == MSG_SET_VIDEO_FRAME_METADATA_LISTENER) { } else if (messageType == MSG_SET_VIDEO_FRAME_METADATA_LISTENER) {
frameMetadataListener = (VideoFrameMetadataListener) message; frameMetadataListener = (VideoFrameMetadataListener) message;
} else { } else {
...@@ -560,7 +558,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { ...@@ -560,7 +558,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
} }
lastRenderTimeUs = C.msToUs(SystemClock.elapsedRealtime() * 1000); lastRenderTimeUs = C.msToUs(SystemClock.elapsedRealtime() * 1000);
int bufferMode = outputBuffer.mode; int bufferMode = outputBuffer.mode;
boolean renderSurface = bufferMode == C.VIDEO_OUTPUT_MODE_SURFACE_YUV && surface != null; boolean renderSurface = bufferMode == C.VIDEO_OUTPUT_MODE_SURFACE_YUV && outputSurface != null;
boolean renderYuv = bufferMode == C.VIDEO_OUTPUT_MODE_YUV && outputBufferRenderer != null; boolean renderYuv = bufferMode == C.VIDEO_OUTPUT_MODE_YUV && outputBufferRenderer != null;
if (!renderYuv && !renderSurface) { if (!renderYuv && !renderSurface) {
dropOutputBuffer(outputBuffer); dropOutputBuffer(outputBuffer);
...@@ -569,7 +567,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { ...@@ -569,7 +567,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
if (renderYuv) { if (renderYuv) {
outputBufferRenderer.setOutputBuffer(outputBuffer); outputBufferRenderer.setOutputBuffer(outputBuffer);
} else { } else {
renderOutputBufferToSurface(outputBuffer, surface); renderOutputBufferToSurface(outputBuffer, outputSurface);
} }
consecutiveDroppedFrameCount = 0; consecutiveDroppedFrameCount = 0;
decoderCounters.renderedOutputBufferCount++; decoderCounters.renderedOutputBufferCount++;
...@@ -590,47 +588,26 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { ...@@ -590,47 +588,26 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
protected abstract void renderOutputBufferToSurface( protected abstract void renderOutputBufferToSurface(
VideoDecoderOutputBuffer outputBuffer, Surface surface) throws DecoderException; VideoDecoderOutputBuffer outputBuffer, Surface surface) throws DecoderException;
/** /** Sets the video output. */
* Sets output surface. protected final void setOutput(@Nullable Object output) {
* if (output instanceof Surface) {
* @param surface Surface. outputSurface = (Surface) output;
*/ outputBufferRenderer = null;
protected final void setOutputSurface(@Nullable Surface surface) { outputMode = C.VIDEO_OUTPUT_MODE_SURFACE_YUV;
if (this.surface != surface) { } else if (output instanceof VideoDecoderOutputBufferRenderer) {
// The output has changed. outputSurface = null;
this.surface = surface; outputBufferRenderer = (VideoDecoderOutputBufferRenderer) output;
if (surface != null) { outputMode = C.VIDEO_OUTPUT_MODE_YUV;
outputBufferRenderer = null; } else {
outputMode = C.VIDEO_OUTPUT_MODE_SURFACE_YUV; // Handle unsupported outputs by clearing the output.
if (decoder != null) { output = null;
setDecoderOutputMode(outputMode); outputSurface = null;
} outputBufferRenderer = null;
onOutputChanged(); outputMode = C.VIDEO_OUTPUT_MODE_NONE;
} else { }
// The output has been removed. We leave the outputMode of the underlying decoder unchanged if (this.output != output) {
// in anticipation that a subsequent output will likely be of the same type. this.output = output;
outputMode = C.VIDEO_OUTPUT_MODE_NONE; if (output != null) {
onOutputRemoved();
}
} else if (surface != null) {
// The output is unchanged and non-null.
onOutputReset();
}
}
/**
* Sets output buffer renderer.
*
* @param outputBufferRenderer Output buffer renderer.
*/
protected final void setOutputBufferRenderer(
@Nullable VideoDecoderOutputBufferRenderer outputBufferRenderer) {
if (this.outputBufferRenderer != outputBufferRenderer) {
// The output has changed.
this.outputBufferRenderer = outputBufferRenderer;
if (outputBufferRenderer != null) {
surface = null;
outputMode = C.VIDEO_OUTPUT_MODE_YUV;
if (decoder != null) { if (decoder != null) {
setDecoderOutputMode(outputMode); setDecoderOutputMode(outputMode);
} }
...@@ -638,10 +615,9 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { ...@@ -638,10 +615,9 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
} else { } else {
// The output has been removed. We leave the outputMode of the underlying decoder unchanged // The output has been removed. We leave the outputMode of the underlying decoder unchanged
// in anticipation that a subsequent output will likely be of the same type. // in anticipation that a subsequent output will likely be of the same type.
outputMode = C.VIDEO_OUTPUT_MODE_NONE;
onOutputRemoved(); onOutputRemoved();
} }
} else if (outputBufferRenderer != null) { } else if (output != null) {
// The output is unchanged and non-null. // The output is unchanged and non-null.
onOutputReset(); onOutputReset();
} }
...@@ -652,7 +628,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { ...@@ -652,7 +628,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
* *
* @param outputMode Output mode. * @param outputMode Output mode.
*/ */
protected abstract void setDecoderOutputMode(@C.VideoOutputMode int outputMode); protected abstract void setDecoderOutputMode(@VideoOutputMode int outputMode);
/** /**
* Evaluates whether the existing decoder can be reused for a new {@link Format}. * Evaluates whether the existing decoder can be reused for a new {@link Format}.
...@@ -927,13 +903,13 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { ...@@ -927,13 +903,13 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
renderedFirstFrameAfterEnable = true; renderedFirstFrameAfterEnable = true;
if (!renderedFirstFrameAfterReset) { if (!renderedFirstFrameAfterReset) {
renderedFirstFrameAfterReset = true; renderedFirstFrameAfterReset = true;
eventDispatcher.renderedFirstFrame(surface); eventDispatcher.renderedFirstFrame(output);
} }
} }
private void maybeRenotifyRenderedFirstFrame() { private void maybeRenotifyRenderedFirstFrame() {
if (renderedFirstFrameAfterReset) { if (renderedFirstFrameAfterReset) {
eventDispatcher.renderedFirstFrame(surface); eventDispatcher.renderedFirstFrame(output);
} }
} }
......
...@@ -76,8 +76,9 @@ import java.util.List; ...@@ -76,8 +76,9 @@ import java.util.List;
* on the playback thread: * on the playback thread:
* *
* <ul> * <ul>
* <li>Message with type {@link #MSG_SET_SURFACE} to set the output surface. The message payload * <li>Message with type {@link #MSG_SET_VIDEO_OUTPUT} to set the output. The message payload
* should be the target {@link Surface}, or null. * should be the target {@link Surface}, or null to clear the output. Other non-null payloads
* have the effect of clearing the output.
* <li>Message with type {@link #MSG_SET_SCALING_MODE} to set the video scaling mode. The message * <li>Message with type {@link #MSG_SET_SCALING_MODE} to set the video scaling mode. The message
* payload should be one of the integer scaling modes in {@link C.VideoScalingMode}. Note that * payload should be one of the integer scaling modes in {@link C.VideoScalingMode}. Note that
* the scaling mode only applies if the {@link Surface} targeted by this renderer is owned by * the scaling mode only applies if the {@link Surface} targeted by this renderer is owned by
...@@ -506,8 +507,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -506,8 +507,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@Override @Override
public void handleMessage(int messageType, @Nullable Object message) throws ExoPlaybackException { public void handleMessage(int messageType, @Nullable Object message) throws ExoPlaybackException {
switch (messageType) { switch (messageType) {
case MSG_SET_SURFACE: case MSG_SET_VIDEO_OUTPUT:
setSurface((Surface) message); setOutput(message);
break; break;
case MSG_SET_SCALING_MODE: case MSG_SET_SCALING_MODE:
scalingMode = (Integer) message; scalingMode = (Integer) message;
...@@ -533,7 +534,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -533,7 +534,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
} }
private void setSurface(Surface surface) throws ExoPlaybackException { private void setOutput(@Nullable Object output) throws ExoPlaybackException {
// Handle unsupported (i.e., non-Surface) outputs by clearing the surface.
@Nullable Surface surface = output instanceof Surface ? (Surface) output : null;
if (surface == null) { if (surface == null) {
// Use a dummy surface if possible. // Use a dummy surface if possible.
if (dummySurface != null) { if (dummySurface != null) {
...@@ -546,6 +550,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -546,6 +550,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
} }
} }
// We only need to update the codec if the surface has changed. // We only need to update the codec if the surface has changed.
if (this.surface != surface) { if (this.surface != surface) {
this.surface = surface; this.surface = surface;
......
...@@ -125,18 +125,14 @@ public interface VideoRendererEventListener { ...@@ -125,18 +125,14 @@ public interface VideoRendererEventListener {
int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {} int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {}
/** /**
* Called when a frame is rendered for the first time since setting the surface, or since the * Called when a frame is rendered for the first time since setting the output, or since the
* renderer was reset, or since the stream being rendered was changed. * renderer was reset, or since the stream being rendered was changed.
* *
* @param surface The {@link Surface} to which a first frame has been rendered, or {@code null} if * @param output The output of the video renderer. Normally a {@link Surface}, however some video
* the renderer renders to something that isn't a {@link Surface}. * renderers may have other output types (e.g., a {@link VideoDecoderOutputBufferRenderer}).
* @param renderTimeMs The {@link SystemClock#elapsedRealtime()} when the frame was rendered. * @param renderTimeMs The {@link SystemClock#elapsedRealtime()} when the frame was rendered.
*/ */
default void onRenderedFirstFrame(@Nullable Surface surface, long renderTimeMs) {} default void onRenderedFirstFrame(Object output, long renderTimeMs) {}
/** @deprecated Use {@link #onRenderedFirstFrame(Surface, long)}. */
@Deprecated
default void onRenderedFirstFrame(@Nullable Surface surface) {}
/** /**
* Called when a decoder is released. * Called when a decoder is released.
...@@ -251,16 +247,12 @@ public interface VideoRendererEventListener { ...@@ -251,16 +247,12 @@ public interface VideoRendererEventListener {
} }
} }
/** Invokes {@link VideoRendererEventListener#onRenderedFirstFrame(Surface, long)}. */ /** Invokes {@link VideoRendererEventListener#onRenderedFirstFrame(Object, long)}. */
public void renderedFirstFrame(@Nullable Surface surface) { public void renderedFirstFrame(Object output) {
if (handler != null) { if (handler != null) {
// TODO: Replace this timestamp with the actual frame release time. // TODO: Replace this timestamp with the actual frame release time.
long renderTimeMs = SystemClock.elapsedRealtime(); long renderTimeMs = SystemClock.elapsedRealtime();
handler.post( handler.post(() -> castNonNull(listener).onRenderedFirstFrame(output, renderTimeMs));
() -> {
castNonNull(listener).onRenderedFirstFrame(surface);
castNonNull(listener).onRenderedFirstFrame(surface, renderTimeMs);
});
} }
} }
......
...@@ -2560,7 +2560,7 @@ public final class ExoPlayerTest { ...@@ -2560,7 +2560,7 @@ public final class ExoPlayerTest {
.start() .start()
.blockUntilActionScheduleFinished(TIMEOUT_MS) .blockUntilActionScheduleFinished(TIMEOUT_MS)
.blockUntilEnded(TIMEOUT_MS); .blockUntilEnded(TIMEOUT_MS);
assertThat(Collections.frequency(rendererMessages, Renderer.MSG_SET_SURFACE)).isEqualTo(2); assertThat(Collections.frequency(rendererMessages, Renderer.MSG_SET_VIDEO_OUTPUT)).isEqualTo(2);
} }
@Test @Test
......
...@@ -2309,8 +2309,7 @@ public final class AnalyticsCollectorTest { ...@@ -2309,8 +2309,7 @@ public final class AnalyticsCollectorTest {
} }
@Override @Override
public void onRenderedFirstFrame( public void onRenderedFirstFrame(EventTime eventTime, Object output, long renderTimeMs) {
EventTime eventTime, @Nullable Surface surface, long renderTimeMs) {
reportedEvents.add(new ReportedEvent(EVENT_RENDERED_FIRST_FRAME, eventTime)); reportedEvents.add(new ReportedEvent(EVENT_RENDERED_FIRST_FRAME, eventTime));
} }
......
...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.video; ...@@ -17,7 +17,8 @@ package com.google.android.exoplayer2.video;
import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM;
import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample; import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
...@@ -170,7 +171,7 @@ public final class DecoderVideoRendererTest { ...@@ -170,7 +171,7 @@ public final class DecoderVideoRendererTest {
}; };
} }
}; };
renderer.setOutputSurface(surface); renderer.setOutput(surface);
} }
@After @After
...@@ -211,7 +212,7 @@ public final class DecoderVideoRendererTest { ...@@ -211,7 +212,7 @@ public final class DecoderVideoRendererTest {
ShadowLooper.idleMainLooper(); ShadowLooper.idleMainLooper();
} }
verify(eventListener).onRenderedFirstFrame(any()); verify(eventListener).onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong());
} }
@Test @Test
...@@ -242,7 +243,7 @@ public final class DecoderVideoRendererTest { ...@@ -242,7 +243,7 @@ public final class DecoderVideoRendererTest {
ShadowLooper.idleMainLooper(); ShadowLooper.idleMainLooper();
} }
verify(eventListener, never()).onRenderedFirstFrame(any()); verify(eventListener, never()).onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong());
} }
@Test @Test
...@@ -273,7 +274,7 @@ public final class DecoderVideoRendererTest { ...@@ -273,7 +274,7 @@ public final class DecoderVideoRendererTest {
ShadowLooper.idleMainLooper(); ShadowLooper.idleMainLooper();
} }
verify(eventListener).onRenderedFirstFrame(any()); verify(eventListener).onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong());
} }
// TODO: Fix rendering of first frame at stream transition. // TODO: Fix rendering of first frame at stream transition.
...@@ -325,7 +326,8 @@ public final class DecoderVideoRendererTest { ...@@ -325,7 +326,8 @@ public final class DecoderVideoRendererTest {
ShadowLooper.idleMainLooper(); ShadowLooper.idleMainLooper();
} }
verify(eventListener, times(2)).onRenderedFirstFrame(any()); verify(eventListener, times(2))
.onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong());
} }
// TODO: Fix rendering of first frame at stream transition. // TODO: Fix rendering of first frame at stream transition.
...@@ -376,11 +378,12 @@ public final class DecoderVideoRendererTest { ...@@ -376,11 +378,12 @@ public final class DecoderVideoRendererTest {
ShadowLooper.idleMainLooper(); ShadowLooper.idleMainLooper();
} }
verify(eventListener).onRenderedFirstFrame(any()); verify(eventListener).onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong());
// Render to streamOffsetUs and verify the new first frame gets rendered. // Render to streamOffsetUs and verify the new first frame gets rendered.
renderer.render(/* positionUs= */ 100, SystemClock.elapsedRealtime() * 1000); renderer.render(/* positionUs= */ 100, SystemClock.elapsedRealtime() * 1000);
verify(eventListener, times(2)).onRenderedFirstFrame(any()); verify(eventListener, times(2))
.onRenderedFirstFrame(eq(surface), /* renderTimeMs= */ anyLong());
} }
} }
...@@ -120,7 +120,7 @@ public class MediaCodecVideoRendererTest { ...@@ -120,7 +120,7 @@ public class MediaCodecVideoRendererTest {
}; };
surface = new Surface(new SurfaceTexture(/* texName= */ 0)); surface = new Surface(new SurfaceTexture(/* texName= */ 0));
mediaCodecVideoRenderer.handleMessage(Renderer.MSG_SET_SURFACE, surface); mediaCodecVideoRenderer.handleMessage(Renderer.MSG_SET_VIDEO_OUTPUT, surface);
} }
@After @After
......
...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.testutil; ...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.testutil;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import android.view.Surface;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
...@@ -35,7 +34,7 @@ public class FakeVideoRenderer extends FakeRenderer { ...@@ -35,7 +34,7 @@ public class FakeVideoRenderer extends FakeRenderer {
private final VideoRendererEventListener.EventDispatcher eventDispatcher; private final VideoRendererEventListener.EventDispatcher eventDispatcher;
private final DecoderCounters decoderCounters; private final DecoderCounters decoderCounters;
private @MonotonicNonNull Format format; private @MonotonicNonNull Format format;
@Nullable private Surface surface; @Nullable private Object output;
private long streamOffsetUs; private long streamOffsetUs;
private boolean renderedFirstFrameAfterReset; private boolean renderedFirstFrameAfterReset;
private boolean mayRenderFirstFrameAfterEnableIfNotStarted; private boolean mayRenderFirstFrameAfterEnableIfNotStarted;
...@@ -97,8 +96,8 @@ public class FakeVideoRenderer extends FakeRenderer { ...@@ -97,8 +96,8 @@ public class FakeVideoRenderer extends FakeRenderer {
@Override @Override
public void handleMessage(int messageType, @Nullable Object payload) throws ExoPlaybackException { public void handleMessage(int messageType, @Nullable Object payload) throws ExoPlaybackException {
switch (messageType) { switch (messageType) {
case MSG_SET_SURFACE: case MSG_SET_VIDEO_OUTPUT:
surface = (Surface) payload; output = payload;
renderedFirstFrameAfterReset = false; renderedFirstFrameAfterReset = false;
break; break;
default: default:
...@@ -110,17 +109,18 @@ public class FakeVideoRenderer extends FakeRenderer { ...@@ -110,17 +109,18 @@ public class FakeVideoRenderer extends FakeRenderer {
protected boolean shouldProcessBuffer(long bufferTimeUs, long playbackPositionUs) { protected boolean shouldProcessBuffer(long bufferTimeUs, long playbackPositionUs) {
boolean shouldProcess = super.shouldProcessBuffer(bufferTimeUs, playbackPositionUs); boolean shouldProcess = super.shouldProcessBuffer(bufferTimeUs, playbackPositionUs);
boolean shouldRenderFirstFrame = boolean shouldRenderFirstFrame =
surface != null output != null
&& (!renderedFirstFrameAfterEnable && (!renderedFirstFrameAfterEnable
? (getState() == Renderer.STATE_STARTED ? (getState() == Renderer.STATE_STARTED
|| mayRenderFirstFrameAfterEnableIfNotStarted) || mayRenderFirstFrameAfterEnableIfNotStarted)
: !renderedFirstFrameAfterReset); : !renderedFirstFrameAfterReset);
shouldProcess |= shouldRenderFirstFrame && playbackPositionUs >= streamOffsetUs; shouldProcess |= shouldRenderFirstFrame && playbackPositionUs >= streamOffsetUs;
if (shouldProcess && !renderedFirstFrameAfterReset && surface != null) { @Nullable Object output = this.output;
if (shouldProcess && !renderedFirstFrameAfterReset && output != null) {
@MonotonicNonNull Format format = Assertions.checkNotNull(this.format); @MonotonicNonNull Format format = Assertions.checkNotNull(this.format);
eventDispatcher.videoSizeChanged( eventDispatcher.videoSizeChanged(
format.width, format.height, format.rotationDegrees, format.pixelWidthHeightRatio); format.width, format.height, format.rotationDegrees, format.pixelWidthHeightRatio);
eventDispatcher.renderedFirstFrame(surface); eventDispatcher.renderedFirstFrame(output);
renderedFirstFrameAfterReset = true; renderedFirstFrameAfterReset = true;
renderedFirstFrameAfterEnable = true; renderedFirstFrameAfterEnable = true;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment