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