Commit 72f4b964 by tonihei Committed by Oliver Woodman

Add parameter to Renderer.enable to allow rendering of first sample.

PiperOrigin-RevId: 295985916
parent c95ed7d1
...@@ -82,13 +82,19 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -82,13 +82,19 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
} }
@Override @Override
public final void enable(RendererConfiguration configuration, Format[] formats, public final void enable(
SampleStream stream, long positionUs, boolean joining, long offsetUs) RendererConfiguration configuration,
Format[] formats,
SampleStream stream,
long positionUs,
boolean joining,
boolean mayRenderStartOfStream,
long offsetUs)
throws ExoPlaybackException { throws ExoPlaybackException {
Assertions.checkState(state == STATE_DISABLED); Assertions.checkState(state == STATE_DISABLED);
this.configuration = configuration; this.configuration = configuration;
state = STATE_ENABLED; state = STATE_ENABLED;
onEnabled(joining); onEnabled(joining, mayRenderStartOfStream);
replaceStream(formats, stream, offsetUs); replaceStream(formats, stream, offsetUs);
onPositionReset(positionUs, joining); onPositionReset(positionUs, joining);
} }
...@@ -193,27 +199,30 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -193,27 +199,30 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
/** /**
* Called when the renderer is enabled. * Called when the renderer is enabled.
* <p> *
* The default implementation is a no-op. * <p>The default implementation is a no-op.
* *
* @param joining Whether this renderer is being enabled to join an ongoing playback. * @param joining Whether this renderer is being enabled to join an ongoing playback.
* @param mayRenderStartOfStream Whether this renderer is allowed to render the start of the
* stream even if the state is not {@link #STATE_STARTED} yet.
* @throws ExoPlaybackException If an error occurs. * @throws ExoPlaybackException If an error occurs.
*/ */
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
throws ExoPlaybackException {
// Do nothing. // Do nothing.
} }
/** /**
* Called when the renderer's stream has changed. This occurs when the renderer is enabled after * Called when the renderer's stream has changed. This occurs when the renderer is enabled after
* {@link #onEnabled(boolean)} has been called, and also when the stream has been replaced whilst * {@link #onEnabled(boolean, boolean)} has been called, and also when the stream has been
* the renderer is enabled or started. * replaced whilst the renderer is enabled or started.
* <p> *
* The default implementation is a no-op. * <p>The default implementation is a no-op.
* *
* @param formats The enabled formats. * @param formats The enabled formats.
* @param offsetUs The offset that will be added to the timestamps of buffers read via * @param offsetUs The offset that will be added to the timestamps of buffers read via {@link
* {@link #readSource(FormatHolder, DecoderInputBuffer, boolean)} so that decoder input * #readSource(FormatHolder, DecoderInputBuffer, boolean)} so that decoder input buffers have
* buffers have monotonically increasing timestamps. * monotonically increasing timestamps.
* @throws ExoPlaybackException If an error occurs. * @throws ExoPlaybackException If an error occurs.
*/ */
protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException { protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException {
......
...@@ -2002,6 +2002,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -2002,6 +2002,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
playingPeriodHolder.sampleStreams[rendererIndex], playingPeriodHolder.sampleStreams[rendererIndex],
rendererPositionUs, rendererPositionUs,
joining, joining,
/* mayRenderStartOfStream= */ true,
playingPeriodHolder.getRendererOffset()); playingPeriodHolder.getRendererOffset());
mediaClock.onRendererEnabled(renderer); mediaClock.onRendererEnabled(renderer);
// Start the renderer if playing. // Start the renderer if playing.
......
...@@ -60,24 +60,15 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities ...@@ -60,24 +60,15 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities
return state; return state;
} }
/**
* Replaces the {@link SampleStream} that will be associated with this renderer.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_DISABLED}.
*
* @param configuration The renderer configuration.
* @param formats The enabled formats. Should be empty.
* @param stream The {@link SampleStream} from which the renderer should consume.
* @param positionUs The player's current position.
* @param joining Whether this renderer is being enabled to join an ongoing playback.
* @param offsetUs The offset that should be subtracted from {@code positionUs}
* to get the playback position with respect to the media.
* @throws ExoPlaybackException If an error occurs.
*/
@Override @Override
public final void enable(RendererConfiguration configuration, Format[] formats, public final void enable(
SampleStream stream, long positionUs, boolean joining, long offsetUs) RendererConfiguration configuration,
Format[] formats,
SampleStream stream,
long positionUs,
boolean joining,
boolean mayRenderStartOfStream,
long offsetUs)
throws ExoPlaybackException { throws ExoPlaybackException {
Assertions.checkState(state == STATE_DISABLED); Assertions.checkState(state == STATE_DISABLED);
this.configuration = configuration; this.configuration = configuration;
...@@ -94,18 +85,6 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities ...@@ -94,18 +85,6 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities
onStarted(); onStarted();
} }
/**
* Replaces the {@link SampleStream} that will be associated with this renderer.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}.
*
* @param formats The enabled formats. Should be empty.
* @param stream The {@link SampleStream} to be associated with this renderer.
* @param offsetUs The offset that should be subtracted from {@code positionUs} in
* {@link #render(long, long)} to get the playback position with respect to the media.
* @throws ExoPlaybackException If an error occurs.
*/
@Override @Override
public final void replaceStream(Format[] formats, SampleStream stream, long offsetUs) public final void replaceStream(Format[] formats, SampleStream stream, long offsetUs)
throws ExoPlaybackException { throws ExoPlaybackException {
......
...@@ -222,21 +222,30 @@ public interface Renderer extends PlayerMessage.Target { ...@@ -222,21 +222,30 @@ public interface Renderer extends PlayerMessage.Target {
/** /**
* Enables the renderer to consume from the specified {@link SampleStream}. * Enables the renderer to consume from the specified {@link SampleStream}.
* <p> *
* This method may be called when the renderer is in the following states: * <p>This method may be called when the renderer is in the following states: {@link
* {@link #STATE_DISABLED}. * #STATE_DISABLED}.
* *
* @param configuration The renderer configuration. * @param configuration The renderer configuration.
* @param formats The enabled formats. * @param formats The enabled formats.
* @param stream The {@link SampleStream} from which the renderer should consume. * @param stream The {@link SampleStream} from which the renderer should consume.
* @param positionUs The player's current position. * @param positionUs The player's current position.
* @param joining Whether this renderer is being enabled to join an ongoing playback. * @param joining Whether this renderer is being enabled to join an ongoing playback.
* @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} * @param mayRenderStartOfStream Whether this renderer is allowed to render the start of the
* before they are rendered. * stream even if the state is not {@link #STATE_STARTED} yet.
* @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} before
* they are rendered.
* @throws ExoPlaybackException If an error occurs. * @throws ExoPlaybackException If an error occurs.
*/ */
void enable(RendererConfiguration configuration, Format[] formats, SampleStream stream, void enable(
long positionUs, boolean joining, long offsetUs) throws ExoPlaybackException; RendererConfiguration configuration,
Format[] formats,
SampleStream stream,
long positionUs,
boolean joining,
boolean mayRenderStartOfStream,
long offsetUs)
throws ExoPlaybackException;
/** /**
* Starts the renderer, meaning that calls to {@link #render(long, long)} will cause media to be * Starts the renderer, meaning that calls to {@link #render(long, long)} will cause media to be
...@@ -341,21 +350,32 @@ public interface Renderer extends PlayerMessage.Target { ...@@ -341,21 +350,32 @@ public interface Renderer extends PlayerMessage.Target {
/** /**
* Incrementally renders the {@link SampleStream}. * Incrementally renders the {@link SampleStream}.
* <p> *
* If the renderer is in the {@link #STATE_ENABLED} state then each call to this method will do * <p>If the renderer is in the {@link #STATE_ENABLED} state then each call to this method will do
* work toward being ready to render the {@link SampleStream} when the renderer is started. It may * work toward being ready to render the {@link SampleStream} when the renderer is started. If the
* also render the very start of the media, for example the first frame of a video stream. If the
* renderer is in the {@link #STATE_STARTED} state then calls to this method will render the * renderer is in the {@link #STATE_STARTED} state then calls to this method will render the
* {@link SampleStream} in sync with the specified media positions. * {@link SampleStream} in sync with the specified media positions.
* <p> *
* This method should return quickly, and should not block if the renderer is unable to make * <p>The renderer may also render the very start of the media at the current position (e.g. the
* first frame of a video stream) while still in the {@link #STATE_ENABLED} state. It's not
* allowed to do that in the following two cases:
*
* <ol>
* <li>The initial start of the media after calling {@link #enable(RendererConfiguration,
* Format[], SampleStream, long, boolean, boolean, long)} with {@code
* mayRenderStartOfStream} set to {@code false}.
* <li>The start of a new stream after calling {@link #replaceStream(Format[], SampleStream,
* long)}.
* </ol>
*
* <p>This method should return quickly, and should not block if the renderer is unable to make
* useful progress. * useful progress.
* <p>
* This method may be called when the renderer is in the following states:
* {@link #STATE_ENABLED}, {@link #STATE_STARTED}.
* *
* @param positionUs The current media time in microseconds, measured at the start of the * <p>This method may be called when the renderer is in the following states: {@link
* current iteration of the rendering loop. * #STATE_ENABLED}, {@link #STATE_STARTED}.
*
* @param positionUs The current media time in microseconds, measured at the start of the current
* iteration of the rendering loop.
* @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
* measured at the start of the current iteration of the rendering loop. * measured at the start of the current iteration of the rendering loop.
* @throws ExoPlaybackException If an error occurs. * @throws ExoPlaybackException If an error occurs.
......
...@@ -491,8 +491,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -491,8 +491,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
@Override @Override
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
super.onEnabled(joining); throws ExoPlaybackException {
super.onEnabled(joining, mayRenderStartOfStream);
eventDispatcher.enabled(decoderCounters); eventDispatcher.enabled(decoderCounters);
int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) { if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
......
...@@ -495,7 +495,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -495,7 +495,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
@Override @Override
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
throws ExoPlaybackException {
decoderCounters = new DecoderCounters(); decoderCounters = new DecoderCounters();
eventDispatcher.enabled(decoderCounters); eventDispatcher.enabled(decoderCounters);
int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; int tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
......
...@@ -679,7 +679,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -679,7 +679,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
@Override @Override
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
throws ExoPlaybackException {
decoderCounters = new DecoderCounters(); decoderCounters = new DecoderCounters();
} }
......
...@@ -129,7 +129,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -129,7 +129,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
private Surface surface; private Surface surface;
private Surface dummySurface; private Surface dummySurface;
@VideoScalingMode private int scalingMode; @VideoScalingMode private int scalingMode;
private boolean renderedFirstFrame; private boolean renderedFirstFrameAfterReset;
private boolean mayRenderFirstFrameAfterEnableIfNotStarted;
private boolean renderedFirstFrameAfterEnable;
private long initialPositionUs; private long initialPositionUs;
private long joiningDeadlineMs; private long joiningDeadlineMs;
private long droppedFrameAccumulationStartTimeMs; private long droppedFrameAccumulationStartTimeMs;
...@@ -360,8 +362,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -360,8 +362,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
@Override @Override
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
super.onEnabled(joining); throws ExoPlaybackException {
super.onEnabled(joining, mayRenderStartOfStream);
int oldTunnelingAudioSessionId = tunnelingAudioSessionId; int oldTunnelingAudioSessionId = tunnelingAudioSessionId;
tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId; tunnelingAudioSessionId = getConfiguration().tunnelingAudioSessionId;
tunneling = tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET; tunneling = tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET;
...@@ -370,6 +373,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -370,6 +373,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
eventDispatcher.enabled(decoderCounters); eventDispatcher.enabled(decoderCounters);
frameReleaseTimeHelper.enable(); frameReleaseTimeHelper.enable();
mayRenderFirstFrameAfterEnableIfNotStarted = mayRenderStartOfStream;
renderedFirstFrameAfterEnable = false;
} }
@Override @Override
...@@ -387,8 +392,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -387,8 +392,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@Override @Override
public boolean isReady() { public boolean isReady() {
if (super.isReady() && (renderedFirstFrame || (dummySurface != null && surface == dummySurface) if (super.isReady()
|| getCodec() == null || tunneling)) { && (renderedFirstFrameAfterReset
|| (dummySurface != null && surface == dummySurface)
|| getCodec() == null
|| tunneling)) {
// Ready. If we were joining then we've now joined, so clear the joining deadline. // Ready. If we were joining then we've now joined, so clear the joining deadline.
joiningDeadlineMs = C.TIME_UNSET; joiningDeadlineMs = C.TIME_UNSET;
return true; return true;
...@@ -729,11 +737,15 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -729,11 +737,15 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
long elapsedRealtimeNowUs = SystemClock.elapsedRealtime() * 1000; long elapsedRealtimeNowUs = SystemClock.elapsedRealtime() * 1000;
long elapsedSinceLastRenderUs = elapsedRealtimeNowUs - lastRenderTimeUs; long elapsedSinceLastRenderUs = elapsedRealtimeNowUs - lastRenderTimeUs;
boolean isStarted = getState() == STATE_STARTED; boolean isStarted = getState() == STATE_STARTED;
boolean shouldRenderFirstFrame =
!renderedFirstFrameAfterEnable
? (isStarted || mayRenderFirstFrameAfterEnableIfNotStarted)
: !renderedFirstFrameAfterReset;
// Don't force output until we joined and the position reached the current stream. // Don't force output until we joined and the position reached the current stream.
boolean forceRenderOutputBuffer = boolean forceRenderOutputBuffer =
joiningDeadlineMs == C.TIME_UNSET joiningDeadlineMs == C.TIME_UNSET
&& positionUs >= outputStreamOffsetUs && positionUs >= outputStreamOffsetUs
&& (!renderedFirstFrame && (shouldRenderFirstFrame
|| (isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedSinceLastRenderUs))); || (isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedSinceLastRenderUs)));
if (forceRenderOutputBuffer) { if (forceRenderOutputBuffer) {
long releaseTimeNs = System.nanoTime(); long releaseTimeNs = System.nanoTime();
...@@ -1056,7 +1068,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -1056,7 +1068,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
private void clearRenderedFirstFrame() { private void clearRenderedFirstFrame() {
renderedFirstFrame = false; renderedFirstFrameAfterReset = false;
// The first frame notification is triggered by renderOutputBuffer or renderOutputBufferV21 for // The first frame notification is triggered by renderOutputBuffer or renderOutputBufferV21 for
// non-tunneled playback, onQueueInputBuffer for tunneled playback prior to API level 23, and // non-tunneled playback, onQueueInputBuffer for tunneled playback prior to API level 23, and
// OnFrameRenderedListenerV23.onFrameRenderedListener for tunneled playback on API level 23 and // OnFrameRenderedListenerV23.onFrameRenderedListener for tunneled playback on API level 23 and
...@@ -1071,14 +1083,15 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -1071,14 +1083,15 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
/* package */ void maybeNotifyRenderedFirstFrame() { /* package */ void maybeNotifyRenderedFirstFrame() {
if (!renderedFirstFrame) { renderedFirstFrameAfterEnable = true;
renderedFirstFrame = true; if (!renderedFirstFrameAfterReset) {
renderedFirstFrameAfterReset = true;
eventDispatcher.renderedFirstFrame(surface); eventDispatcher.renderedFirstFrame(surface);
} }
} }
private void maybeRenotifyRenderedFirstFrame() { private void maybeRenotifyRenderedFirstFrame() {
if (renderedFirstFrame) { if (renderedFirstFrameAfterReset) {
eventDispatcher.renderedFirstFrame(surface); eventDispatcher.renderedFirstFrame(surface);
} }
} }
......
...@@ -92,7 +92,9 @@ public abstract class SimpleDecoderVideoRenderer extends BaseRenderer { ...@@ -92,7 +92,9 @@ public abstract class SimpleDecoderVideoRenderer extends BaseRenderer {
@ReinitializationState private int decoderReinitializationState; @ReinitializationState private int decoderReinitializationState;
private boolean decoderReceivedBuffers; private boolean decoderReceivedBuffers;
private boolean renderedFirstFrame; private boolean renderedFirstFrameAfterReset;
private boolean mayRenderFirstFrameAfterEnableIfNotStarted;
private boolean renderedFirstFrameAfterEnable;
private long initialPositionUs; private long initialPositionUs;
private long joiningDeadlineMs; private long joiningDeadlineMs;
private boolean waitingForKeys; private boolean waitingForKeys;
...@@ -195,7 +197,7 @@ public abstract class SimpleDecoderVideoRenderer extends BaseRenderer { ...@@ -195,7 +197,7 @@ public abstract class SimpleDecoderVideoRenderer extends BaseRenderer {
} }
if (inputFormat != null if (inputFormat != null
&& (isSourceReady() || outputBuffer != null) && (isSourceReady() || outputBuffer != null)
&& (renderedFirstFrame || !hasOutput())) { && (renderedFirstFrameAfterReset || !hasOutput())) {
// Ready. If we were joining then we've now joined, so clear the joining deadline. // Ready. If we were joining then we've now joined, so clear the joining deadline.
joiningDeadlineMs = C.TIME_UNSET; joiningDeadlineMs = C.TIME_UNSET;
return true; return true;
...@@ -215,9 +217,12 @@ public abstract class SimpleDecoderVideoRenderer extends BaseRenderer { ...@@ -215,9 +217,12 @@ public abstract class SimpleDecoderVideoRenderer extends BaseRenderer {
// Protected methods. // Protected methods.
@Override @Override
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
throws ExoPlaybackException {
decoderCounters = new DecoderCounters(); decoderCounters = new DecoderCounters();
eventDispatcher.enabled(decoderCounters); eventDispatcher.enabled(decoderCounters);
mayRenderFirstFrameAfterEnableIfNotStarted = mayRenderStartOfStream;
renderedFirstFrameAfterEnable = false;
} }
@Override @Override
...@@ -267,6 +272,9 @@ public abstract class SimpleDecoderVideoRenderer extends BaseRenderer { ...@@ -267,6 +272,9 @@ public abstract class SimpleDecoderVideoRenderer extends BaseRenderer {
@Override @Override
protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException { protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException {
// TODO: This shouldn't just update the output stream offset as long as there are still buffers
// of the previous stream in the decoder. It should also make sure to render the first frame of
// the next stream if the playback position reached the new stream and the renderer is started.
outputStreamOffsetUs = offsetUs; outputStreamOffsetUs = offsetUs;
super.onStreamChanged(formats, offsetUs); super.onStreamChanged(formats, offsetUs);
} }
...@@ -787,10 +795,15 @@ public abstract class SimpleDecoderVideoRenderer extends BaseRenderer { ...@@ -787,10 +795,15 @@ public abstract class SimpleDecoderVideoRenderer extends BaseRenderer {
} }
long elapsedRealtimeNowUs = SystemClock.elapsedRealtime() * 1000; long elapsedRealtimeNowUs = SystemClock.elapsedRealtime() * 1000;
long elapsedSinceLastRenderUs = elapsedRealtimeNowUs - lastRenderTimeUs;
boolean isStarted = getState() == STATE_STARTED; boolean isStarted = getState() == STATE_STARTED;
if (!renderedFirstFrame boolean shouldRenderFirstFrame =
|| (isStarted !renderedFirstFrameAfterEnable
&& shouldForceRenderOutputBuffer(earlyUs, elapsedRealtimeNowUs - lastRenderTimeUs))) { ? (isStarted || mayRenderFirstFrameAfterEnableIfNotStarted)
: !renderedFirstFrameAfterReset;
// TODO: We shouldn't force render while we are joining an ongoing playback.
if (shouldRenderFirstFrame
|| (isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedSinceLastRenderUs))) {
renderOutputBuffer(outputBuffer, presentationTimeUs, outputFormat); renderOutputBuffer(outputBuffer, presentationTimeUs, outputFormat);
return true; return true;
} }
...@@ -799,6 +812,7 @@ public abstract class SimpleDecoderVideoRenderer extends BaseRenderer { ...@@ -799,6 +812,7 @@ public abstract class SimpleDecoderVideoRenderer extends BaseRenderer {
return false; return false;
} }
// TODO: Treat dropped buffers as skipped while we are joining an ongoing playback.
if (shouldDropBuffersToKeyframe(earlyUs, elapsedRealtimeUs) if (shouldDropBuffersToKeyframe(earlyUs, elapsedRealtimeUs)
&& maybeDropBuffersToKeyframe(positionUs)) { && maybeDropBuffersToKeyframe(positionUs)) {
return false; return false;
...@@ -862,18 +876,19 @@ public abstract class SimpleDecoderVideoRenderer extends BaseRenderer { ...@@ -862,18 +876,19 @@ public abstract class SimpleDecoderVideoRenderer extends BaseRenderer {
} }
private void clearRenderedFirstFrame() { private void clearRenderedFirstFrame() {
renderedFirstFrame = false; renderedFirstFrameAfterReset = false;
} }
private void maybeNotifyRenderedFirstFrame() { private void maybeNotifyRenderedFirstFrame() {
if (!renderedFirstFrame) { renderedFirstFrameAfterEnable = true;
renderedFirstFrame = true; if (!renderedFirstFrameAfterReset) {
renderedFirstFrameAfterReset = true;
eventDispatcher.renderedFirstFrame(surface); eventDispatcher.renderedFirstFrame(surface);
} }
} }
private void maybeRenotifyRenderedFirstFrame() { private void maybeRenotifyRenderedFirstFrame() {
if (renderedFirstFrame) { if (renderedFirstFrameAfterReset) {
eventDispatcher.renderedFirstFrame(surface); eventDispatcher.renderedFirstFrame(surface);
} }
} }
......
...@@ -5606,7 +5606,8 @@ public final class ExoPlayerTest { ...@@ -5606,7 +5606,8 @@ public final class ExoPlayerTest {
FakeRenderer audioRenderer = FakeRenderer audioRenderer =
new FakeRenderer(Builder.AUDIO_FORMAT) { new FakeRenderer(Builder.AUDIO_FORMAT) {
@Override @Override
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
throws ExoPlaybackException {
// Fail when enabling the renderer. This will happen during the period transition. // Fail when enabling the renderer. This will happen during the period transition.
throw createRendererException(new IllegalStateException(), Builder.AUDIO_FORMAT); throw createRendererException(new IllegalStateException(), Builder.AUDIO_FORMAT);
} }
...@@ -5672,7 +5673,8 @@ public final class ExoPlayerTest { ...@@ -5672,7 +5673,8 @@ public final class ExoPlayerTest {
FakeRenderer audioRenderer = FakeRenderer audioRenderer =
new FakeRenderer(Builder.AUDIO_FORMAT) { new FakeRenderer(Builder.AUDIO_FORMAT) {
@Override @Override
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
throws ExoPlaybackException {
// Fail when enabling the renderer. This will happen during the playlist update. // Fail when enabling the renderer. This will happen during the playlist update.
throw createRendererException(new IllegalStateException(), Builder.AUDIO_FORMAT); throw createRendererException(new IllegalStateException(), Builder.AUDIO_FORMAT);
} }
......
...@@ -241,8 +241,8 @@ public final class AnalyticsCollectorTest { ...@@ -241,8 +241,8 @@ public final class AnalyticsCollectorTest {
period0 /* audio */, period0 /* video */, period1 /* audio */, period1 /* video */); period0 /* audio */, period0 /* video */, period1 /* audio */, period1 /* video */);
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0); assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period1); assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(period1);
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0, period1);
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0); assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0, period1);
assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)).containsExactly(period1); assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)).containsExactly(period1);
listener.assertNoMoreEvents(); listener.assertNoMoreEvents();
} }
...@@ -444,8 +444,10 @@ public final class AnalyticsCollectorTest { ...@@ -444,8 +444,10 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1Seq2); assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1Seq2);
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)) assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES))
.containsExactly(period0, period1Seq2, period1Seq2); .containsExactly(period0, period1Seq2, period1Seq2);
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(period0, period0); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(period0, period0); .containsExactly(period0, period1Seq1, period0, period1Seq2);
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
.containsExactly(period0, period1Seq1, period0, period1Seq2);
assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)) assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
.containsExactly(period0, period1Seq2, period1Seq2); .containsExactly(period0, period1Seq2, period1Seq2);
listener.assertNoMoreEvents(); listener.assertNoMoreEvents();
...@@ -672,9 +674,9 @@ public final class AnalyticsCollectorTest { ...@@ -672,9 +674,9 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(window0Period1Seq0); assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(window0Period1Seq0);
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(window0Period1Seq0); assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(window0Period1Seq0);
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
.containsExactly(window0Period1Seq0, period1Seq0); .containsExactly(window0Period1Seq0, window1Period0Seq1, period1Seq0);
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
.containsExactly(window0Period1Seq0, period1Seq0); .containsExactly(window0Period1Seq0, window1Period0Seq1, period1Seq0);
assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)) assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
.containsExactly(window0Period1Seq0); .containsExactly(window0Period1Seq0);
listener.assertNoMoreEvents(); listener.assertNoMoreEvents();
...@@ -964,8 +966,22 @@ public final class AnalyticsCollectorTest { ...@@ -964,8 +966,22 @@ public final class AnalyticsCollectorTest {
contentAfterPostroll); contentAfterPostroll);
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)) assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES))
.containsExactly(contentAfterPreroll, contentAfterMidroll, contentAfterPostroll); .containsExactly(contentAfterPreroll, contentAfterMidroll, contentAfterPostroll);
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)).containsExactly(prerollAd); assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)).containsExactly(prerollAd); .containsExactly(
prerollAd,
contentAfterPreroll,
midrollAd,
contentAfterMidroll,
postrollAd,
contentAfterPostroll);
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
.containsExactly(
prerollAd,
contentAfterPreroll,
midrollAd,
contentAfterMidroll,
postrollAd,
contentAfterPostroll);
assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)) assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
.containsExactly(contentAfterPreroll, contentAfterMidroll, contentAfterPostroll); .containsExactly(contentAfterPreroll, contentAfterMidroll, contentAfterPostroll);
listener.assertNoMoreEvents(); listener.assertNoMoreEvents();
...@@ -1082,9 +1098,9 @@ public final class AnalyticsCollectorTest { ...@@ -1082,9 +1098,9 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(contentBeforeMidroll); assertThat(listener.getEvents(EVENT_DECODER_DISABLED)).containsExactly(contentBeforeMidroll);
assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(contentAfterMidroll); assertThat(listener.getEvents(EVENT_DROPPED_VIDEO_FRAMES)).containsExactly(contentAfterMidroll);
assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED)) assertThat(listener.getEvents(EVENT_VIDEO_SIZE_CHANGED))
.containsExactly(contentBeforeMidroll, midrollAd); .containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll);
assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME)) assertThat(listener.getEvents(EVENT_RENDERED_FIRST_FRAME))
.containsExactly(contentBeforeMidroll, midrollAd); .containsExactly(contentBeforeMidroll, midrollAd, contentAfterMidroll);
assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET)) assertThat(listener.getEvents(EVENT_VIDEO_FRAME_PROCESSING_OFFSET))
.containsExactly(contentAfterMidroll); .containsExactly(contentAfterMidroll);
listener.assertNoMoreEvents(); listener.assertNoMoreEvents();
...@@ -1194,7 +1210,10 @@ public final class AnalyticsCollectorTest { ...@@ -1194,7 +1210,10 @@ public final class AnalyticsCollectorTest {
private final VideoRendererEventListener.EventDispatcher eventDispatcher; private final VideoRendererEventListener.EventDispatcher eventDispatcher;
private final DecoderCounters decoderCounters; private final DecoderCounters decoderCounters;
private Format format; private Format format;
private boolean renderedFirstFrame; private long streamOffsetUs;
private boolean renderedFirstFrameAfterReset;
private boolean mayRenderFirstFrameAfterStreamChangeIfNotStarted;
private boolean renderedFirstFrameAfterStreamChange;
public FakeVideoRenderer(Handler handler, VideoRendererEventListener eventListener) { public FakeVideoRenderer(Handler handler, VideoRendererEventListener eventListener) {
super(ExoPlayerTestRunner.Builder.VIDEO_FORMAT); super(ExoPlayerTestRunner.Builder.VIDEO_FORMAT);
...@@ -1203,10 +1222,23 @@ public final class AnalyticsCollectorTest { ...@@ -1203,10 +1222,23 @@ public final class AnalyticsCollectorTest {
} }
@Override @Override
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
super.onEnabled(joining); throws ExoPlaybackException {
super.onEnabled(joining, mayRenderStartOfStream);
eventDispatcher.enabled(decoderCounters); eventDispatcher.enabled(decoderCounters);
renderedFirstFrame = false; mayRenderFirstFrameAfterStreamChangeIfNotStarted = mayRenderStartOfStream;
renderedFirstFrameAfterStreamChange = false;
}
@Override
protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException {
super.onStreamChanged(formats, offsetUs);
streamOffsetUs = offsetUs;
if (renderedFirstFrameAfterReset) {
renderedFirstFrameAfterReset = false;
renderedFirstFrameAfterStreamChange = false;
mayRenderFirstFrameAfterStreamChangeIfNotStarted = false;
}
} }
@Override @Override
...@@ -1226,7 +1258,7 @@ public final class AnalyticsCollectorTest { ...@@ -1226,7 +1258,7 @@ public final class AnalyticsCollectorTest {
@Override @Override
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
super.onPositionReset(positionUs, joining); super.onPositionReset(positionUs, joining);
renderedFirstFrame = false; renderedFirstFrameAfterReset = false;
} }
@Override @Override
...@@ -1242,11 +1274,18 @@ public final class AnalyticsCollectorTest { ...@@ -1242,11 +1274,18 @@ public final class AnalyticsCollectorTest {
@Override @Override
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);
if (shouldProcess && !renderedFirstFrame) { boolean shouldRenderFirstFrame =
!renderedFirstFrameAfterStreamChange
? (getState() == Renderer.STATE_STARTED
|| mayRenderFirstFrameAfterStreamChangeIfNotStarted)
: !renderedFirstFrameAfterReset;
shouldProcess |= shouldRenderFirstFrame && playbackPositionUs >= streamOffsetUs;
if (shouldProcess && !renderedFirstFrameAfterReset) {
eventDispatcher.videoSizeChanged( eventDispatcher.videoSizeChanged(
format.width, format.height, format.rotationDegrees, format.pixelWidthHeightRatio); format.width, format.height, format.rotationDegrees, format.pixelWidthHeightRatio);
eventDispatcher.renderedFirstFrame(/* surface= */ null); eventDispatcher.renderedFirstFrame(/* surface= */ null);
renderedFirstFrame = true; renderedFirstFrameAfterReset = true;
renderedFirstFrameAfterStreamChange = true;
} }
return shouldProcess; return shouldProcess;
} }
...@@ -1265,8 +1304,9 @@ public final class AnalyticsCollectorTest { ...@@ -1265,8 +1304,9 @@ public final class AnalyticsCollectorTest {
} }
@Override @Override
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
super.onEnabled(joining); throws ExoPlaybackException {
super.onEnabled(joining, mayRenderStartOfStream);
eventDispatcher.enabled(decoderCounters); eventDispatcher.enabled(decoderCounters);
notifiedAudioSessionId = false; notifiedAudioSessionId = false;
} }
......
...@@ -97,9 +97,10 @@ public class SimpleDecoderAudioRendererTest { ...@@ -97,9 +97,10 @@ public class SimpleDecoderAudioRendererTest {
RendererConfiguration.DEFAULT, RendererConfiguration.DEFAULT,
new Format[] {FORMAT}, new Format[] {FORMAT},
new FakeSampleStream(FORMAT, /* eventDispatcher= */ null, /* shouldOutputSample= */ false), new FakeSampleStream(FORMAT, /* eventDispatcher= */ null, /* shouldOutputSample= */ false),
0, /* positionUs= */ 0,
false, /* joining= */ false,
0); /* mayRenderStartOfStream= */ true,
/* offsetUs= */ 0);
audioRenderer.setCurrentStreamFinal(); audioRenderer.setCurrentStreamFinal();
when(mockAudioSink.isEnded()).thenReturn(true); when(mockAudioSink.isEnded()).thenReturn(true);
while (!audioRenderer.isEnded()) { while (!audioRenderer.isEnded()) {
......
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