Commit bf5e6c78 by ibaker Committed by Oliver Woodman

Pass startPositionUs into Renderer.replaceStream

Plumb this down into BaseRenderer.onStreamChanged and use it when
deciding whether to render the first frame of a new period.

PiperOrigin-RevId: 321175627
parent 5c4b8085
Showing with 133 additions and 147 deletions
...@@ -96,6 +96,8 @@ ...@@ -96,6 +96,8 @@
([#7590](https://github.com/google/ExoPlayer/issues/7590)). ([#7590](https://github.com/google/ExoPlayer/issues/7590)).
* Remove `AdaptiveTrackSelection.minTimeBetweenBufferReevaluationMs` * Remove `AdaptiveTrackSelection.minTimeBetweenBufferReevaluationMs`
parameter ([#7582](https://github.com/google/ExoPlayer/issues/7582)). parameter ([#7582](https://github.com/google/ExoPlayer/issues/7582)).
* Distinguish between `offsetUs` and `startPositionUs` when passing new
`SampleStreams` to `Renderers`.
* Video: Pass frame rate hint to `Surface.setFrameRate` on Android R devices. * Video: Pass frame rate hint to `Surface.setFrameRate` on Android R devices.
* Track selection: * Track selection:
* Add `Player.getTrackSelector`. * Add `Player.getTrackSelector`.
......
...@@ -36,7 +36,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -36,7 +36,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
private SampleStream stream; private SampleStream stream;
private Format[] streamFormats; private Format[] streamFormats;
private long streamOffsetUs; private long streamOffsetUs;
private long startPositionUs; private long lastResetPositionUs;
private long readingPositionUs; private long readingPositionUs;
private boolean streamIsFinal; private boolean streamIsFinal;
private boolean throwRendererExceptionIsExecuting; private boolean throwRendererExceptionIsExecuting;
...@@ -85,14 +85,15 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -85,14 +85,15 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
long positionUs, long positionUs,
boolean joining, boolean joining,
boolean mayRenderStartOfStream, boolean mayRenderStartOfStream,
long startPositionUs,
long offsetUs) 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;
startPositionUs = positionUs; lastResetPositionUs = positionUs;
onEnabled(joining, mayRenderStartOfStream); onEnabled(joining, mayRenderStartOfStream);
replaceStream(formats, stream, offsetUs); replaceStream(formats, stream, startPositionUs, offsetUs);
onPositionReset(positionUs, joining); onPositionReset(positionUs, joining);
} }
...@@ -104,14 +105,15 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -104,14 +105,15 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
} }
@Override @Override
public final void replaceStream(Format[] formats, SampleStream stream, long offsetUs) public final void replaceStream(
Format[] formats, SampleStream stream, long startPositionUs, long offsetUs)
throws ExoPlaybackException { throws ExoPlaybackException {
Assertions.checkState(!streamIsFinal); Assertions.checkState(!streamIsFinal);
this.stream = stream; this.stream = stream;
readingPositionUs = offsetUs; readingPositionUs = startPositionUs;
streamFormats = formats; streamFormats = formats;
streamOffsetUs = offsetUs; streamOffsetUs = offsetUs;
onStreamChanged(formats, offsetUs); onStreamChanged(formats, startPositionUs, offsetUs);
} }
@Override @Override
...@@ -148,7 +150,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -148,7 +150,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
@Override @Override
public final void resetPosition(long positionUs) throws ExoPlaybackException { public final void resetPosition(long positionUs) throws ExoPlaybackException {
streamIsFinal = false; streamIsFinal = false;
startPositionUs = positionUs; lastResetPositionUs = positionUs;
readingPositionUs = positionUs; readingPositionUs = positionUs;
onPositionReset(positionUs, false); onPositionReset(positionUs, false);
} }
...@@ -218,24 +220,26 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -218,24 +220,26 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
* <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 startPositionUs The start position of the new stream in renderer time (microseconds).
* @param offsetUs The offset that will be added to the timestamps of buffers read via {@link * @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 * #readSource(FormatHolder, DecoderInputBuffer, boolean)} so that decoder input 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 startPositionUs, long offsetUs)
throws ExoPlaybackException {
// Do nothing. // Do nothing.
} }
/** /**
* Called when the position is reset. This occurs when the renderer is enabled after * Called when the position is reset. This occurs when the renderer is enabled after {@link
* {@link #onStreamChanged(Format[], long)} has been called, and also when a position * #onStreamChanged(Format[], long, long)} has been called, and also when a position discontinuity
* discontinuity is encountered. * is encountered.
* <p> *
* After a position reset, the renderer's {@link SampleStream} is guaranteed to provide samples * <p>After a position reset, the renderer's {@link SampleStream} is guaranteed to provide samples
* starting from a key frame. * starting from a key frame.
* <p> *
* The default implementation is a no-op. * <p>The default implementation is a no-op.
* *
* @param positionUs The new playback position in microseconds. * @param positionUs The new playback position in microseconds.
* @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.
...@@ -289,8 +293,8 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities { ...@@ -289,8 +293,8 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
* Returns the position passed to the most recent call to {@link #enable} or {@link * Returns the position passed to the most recent call to {@link #enable} or {@link
* #resetPosition}. * #resetPosition}.
*/ */
protected final long getStartPositionUs() { protected final long getLastResetPositionUs() {
return startPositionUs; return lastResetPositionUs;
} }
/** Returns a clear {@link FormatHolder}. */ /** Returns a clear {@link FormatHolder}. */
......
...@@ -1873,7 +1873,10 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -1873,7 +1873,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
// The renderer stream is not final, so we can replace the sample streams immediately. // The renderer stream is not final, so we can replace the sample streams immediately.
Format[] formats = getFormats(newTrackSelectorResult.selections.get(i)); Format[] formats = getFormats(newTrackSelectorResult.selections.get(i));
renderer.replaceStream( renderer.replaceStream(
formats, readingPeriodHolder.sampleStreams[i], readingPeriodHolder.getRendererOffset()); formats,
readingPeriodHolder.sampleStreams[i],
readingPeriodHolder.getStartPositionRendererTime(),
readingPeriodHolder.getRendererOffset());
} else if (renderer.isEnded()) { } else if (renderer.isEnded()) {
// The renderer has finished playback, so we can disable it now. // The renderer has finished playback, so we can disable it now.
disableRenderer(renderer); disableRenderer(renderer);
...@@ -2128,6 +2131,7 @@ import java.util.concurrent.atomic.AtomicBoolean; ...@@ -2128,6 +2131,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
rendererPositionUs, rendererPositionUs,
joining, joining,
mayRenderStartOfStream, mayRenderStartOfStream,
periodHolder.getStartPositionRendererTime(),
periodHolder.getRendererOffset()); periodHolder.getRendererOffset());
renderer.handleMessage( renderer.handleMessage(
......
...@@ -68,13 +68,14 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities ...@@ -68,13 +68,14 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities
long positionUs, long positionUs,
boolean joining, boolean joining,
boolean mayRenderStartOfStream, boolean mayRenderStartOfStream,
long startPositionUs,
long offsetUs) 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);
replaceStream(formats, stream, offsetUs); replaceStream(formats, stream, startPositionUs, offsetUs);
onPositionReset(positionUs, joining); onPositionReset(positionUs, joining);
} }
...@@ -86,7 +87,8 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities ...@@ -86,7 +87,8 @@ public abstract class NoSampleRenderer implements Renderer, RendererCapabilities
} }
@Override @Override
public final void replaceStream(Format[] formats, SampleStream stream, long offsetUs) public final void replaceStream(
Format[] formats, SampleStream stream, long startPositionUs, long offsetUs)
throws ExoPlaybackException { throws ExoPlaybackException {
Assertions.checkState(!streamIsFinal); Assertions.checkState(!streamIsFinal);
this.stream = stream; this.stream = stream;
......
...@@ -292,6 +292,7 @@ public interface Renderer extends PlayerMessage.Target { ...@@ -292,6 +292,7 @@ public interface Renderer extends PlayerMessage.Target {
* @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 * @param mayRenderStartOfStream Whether this renderer is allowed to render the start of the
* stream even if the state is not {@link #STATE_STARTED} yet. * stream even if the state is not {@link #STATE_STARTED} yet.
* @param startPositionUs The start position of the stream in renderer time (microseconds).
* @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} before * @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} before
* they are rendered. * they are rendered.
* @throws ExoPlaybackException If an error occurs. * @throws ExoPlaybackException If an error occurs.
...@@ -303,6 +304,7 @@ public interface Renderer extends PlayerMessage.Target { ...@@ -303,6 +304,7 @@ public interface Renderer extends PlayerMessage.Target {
long positionUs, long positionUs,
boolean joining, boolean joining,
boolean mayRenderStartOfStream, boolean mayRenderStartOfStream,
long startPositionUs,
long offsetUs) long offsetUs)
throws ExoPlaybackException; throws ExoPlaybackException;
...@@ -319,17 +321,18 @@ public interface Renderer extends PlayerMessage.Target { ...@@ -319,17 +321,18 @@ public interface Renderer extends PlayerMessage.Target {
/** /**
* Replaces the {@link SampleStream} from which samples will be consumed. * Replaces the {@link SampleStream} from which samples will be consumed.
* <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_ENABLED}, {@link #STATE_STARTED}. * #STATE_ENABLED}, {@link #STATE_STARTED}.
* *
* @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 startPositionUs The start position of the new stream in renderer time (microseconds).
* @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} before * @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} before
* they are rendered. * they are rendered.
* @throws ExoPlaybackException If an error occurs. * @throws ExoPlaybackException If an error occurs.
*/ */
void replaceStream(Format[] formats, SampleStream stream, long offsetUs) void replaceStream(Format[] formats, SampleStream stream, long startPositionUs, long offsetUs)
throws ExoPlaybackException; throws ExoPlaybackException;
/** Returns the {@link SampleStream} being consumed, or null if the renderer is disabled. */ /** Returns the {@link SampleStream} being consumed, or null if the renderer is disabled. */
...@@ -345,7 +348,7 @@ public interface Renderer extends PlayerMessage.Target { ...@@ -345,7 +348,7 @@ public interface Renderer extends PlayerMessage.Target {
boolean hasReadStreamToEnd(); boolean hasReadStreamToEnd();
/** /**
* Returns the playback position up to which the renderer has read samples from the current {@link * Returns the renderer time up to which the renderer has read samples from the current {@link
* SampleStream}, in microseconds, or {@link C#TIME_END_OF_SOURCE} if the renderer has read the * SampleStream}, in microseconds, or {@link C#TIME_END_OF_SOURCE} if the renderer has read the
* current {@link SampleStream} to the end. * current {@link SampleStream} to the end.
* *
...@@ -418,8 +421,8 @@ public interface Renderer extends PlayerMessage.Target { ...@@ -418,8 +421,8 @@ public interface Renderer extends PlayerMessage.Target {
* <p>The renderer may also render the very start of the media at the current position (e.g. the * <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, unless it's the * first frame of a video stream) while still in the {@link #STATE_ENABLED} state, unless it's the
* initial start of the media after calling {@link #enable(RendererConfiguration, Format[], * initial start of the media after calling {@link #enable(RendererConfiguration, Format[],
* SampleStream, long, boolean, boolean, long)} with {@code mayRenderStartOfStream} set to {@code * SampleStream, long, boolean, boolean, long, long)} with {@code mayRenderStartOfStream} set to
* false}. * {@code false}.
* *
* <p>This method should return quickly, and should not block if the renderer is unable to make * <p>This method should return quickly, and should not block if the renderer is unable to make
* useful progress. * useful progress.
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2.mediacodec; package com.google.android.exoplayer2.mediacodec;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodec.CodecException; import android.media.MediaCodec.CodecException;
...@@ -350,6 +352,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -350,6 +352,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private final TimedValueQueue<Format> formatQueue; private final TimedValueQueue<Format> formatQueue;
private final ArrayList<Long> decodeOnlyPresentationTimestamps; private final ArrayList<Long> decodeOnlyPresentationTimestamps;
private final MediaCodec.BufferInfo outputBufferInfo; private final MediaCodec.BufferInfo outputBufferInfo;
private final long[] pendingOutputStreamStartPositionsUs;
private final long[] pendingOutputStreamOffsetsUs; private final long[] pendingOutputStreamOffsetsUs;
private final long[] pendingOutputStreamSwitchTimesUs; private final long[] pendingOutputStreamSwitchTimesUs;
...@@ -406,6 +409,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -406,6 +409,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@MediaCodecOperationMode private int mediaCodecOperationMode; @MediaCodecOperationMode private int mediaCodecOperationMode;
@Nullable private ExoPlaybackException pendingPlaybackException; @Nullable private ExoPlaybackException pendingPlaybackException;
protected DecoderCounters decoderCounters; protected DecoderCounters decoderCounters;
private long outputStreamStartPositionUs;
private long outputStreamOffsetUs; private long outputStreamOffsetUs;
private int pendingOutputStreamOffsetCount; private int pendingOutputStreamOffsetCount;
private boolean receivedOutputMediaFormatChange; private boolean receivedOutputMediaFormatChange;
...@@ -438,8 +442,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -438,8 +442,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
operatingRate = 1f; operatingRate = 1f;
renderTimeLimitMs = C.TIME_UNSET; renderTimeLimitMs = C.TIME_UNSET;
mediaCodecOperationMode = OPERATION_MODE_SYNCHRONOUS; mediaCodecOperationMode = OPERATION_MODE_SYNCHRONOUS;
pendingOutputStreamStartPositionsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT]; pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
pendingOutputStreamSwitchTimesUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT]; pendingOutputStreamSwitchTimesUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
outputStreamStartPositionUs = C.TIME_UNSET;
outputStreamOffsetUs = C.TIME_UNSET; outputStreamOffsetUs = C.TIME_UNSET;
bypassBatchBuffer = new BatchBuffer(); bypassBatchBuffer = new BatchBuffer();
resetCodecStateForRelease(); resetCodecStateForRelease();
...@@ -676,9 +682,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -676,9 +682,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
@Override @Override
protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException { protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs)
if (outputStreamOffsetUs == C.TIME_UNSET) { throws ExoPlaybackException {
outputStreamOffsetUs = offsetUs; if (this.outputStreamOffsetUs == C.TIME_UNSET) {
checkState(this.outputStreamStartPositionUs == C.TIME_UNSET);
this.outputStreamStartPositionUs = startPositionUs;
this.outputStreamOffsetUs = offsetUs;
} else { } else {
if (pendingOutputStreamOffsetCount == pendingOutputStreamOffsetsUs.length) { if (pendingOutputStreamOffsetCount == pendingOutputStreamOffsetsUs.length) {
Log.w( Log.w(
...@@ -688,6 +697,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -688,6 +697,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} else { } else {
pendingOutputStreamOffsetCount++; pendingOutputStreamOffsetCount++;
} }
pendingOutputStreamStartPositionsUs[pendingOutputStreamOffsetCount - 1] = startPositionUs;
pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1] = offsetUs; pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1] = offsetUs;
pendingOutputStreamSwitchTimesUs[pendingOutputStreamOffsetCount - 1] = pendingOutputStreamSwitchTimesUs[pendingOutputStreamOffsetCount - 1] =
largestQueuedPresentationTimeUs; largestQueuedPresentationTimeUs;
...@@ -713,6 +723,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -713,6 +723,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
formatQueue.clear(); formatQueue.clear();
if (pendingOutputStreamOffsetCount != 0) { if (pendingOutputStreamOffsetCount != 0) {
outputStreamOffsetUs = pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1]; outputStreamOffsetUs = pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1];
outputStreamStartPositionUs =
pendingOutputStreamStartPositionsUs[pendingOutputStreamOffsetCount - 1];
pendingOutputStreamOffsetCount = 0; pendingOutputStreamOffsetCount = 0;
} }
} }
...@@ -730,6 +742,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -730,6 +742,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Override @Override
protected void onDisabled() { protected void onDisabled() {
inputFormat = null; inputFormat = null;
outputStreamStartPositionUs = C.TIME_UNSET;
outputStreamOffsetUs = C.TIME_UNSET; outputStreamOffsetUs = C.TIME_UNSET;
pendingOutputStreamOffsetCount = 0; pendingOutputStreamOffsetCount = 0;
if (sourceDrmSession != null || codecDrmSession != null) { if (sourceDrmSession != null || codecDrmSession != null) {
...@@ -1562,9 +1575,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1562,9 +1575,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
protected void onProcessedOutputBuffer(long presentationTimeUs) { protected void onProcessedOutputBuffer(long presentationTimeUs) {
while (pendingOutputStreamOffsetCount != 0 while (pendingOutputStreamOffsetCount != 0
&& presentationTimeUs >= pendingOutputStreamSwitchTimesUs[0]) { && presentationTimeUs >= pendingOutputStreamSwitchTimesUs[0]) {
outputStreamStartPositionUs = pendingOutputStreamStartPositionsUs[0];
outputStreamOffsetUs = pendingOutputStreamOffsetsUs[0]; outputStreamOffsetUs = pendingOutputStreamOffsetsUs[0];
pendingOutputStreamOffsetCount--; pendingOutputStreamOffsetCount--;
System.arraycopy( System.arraycopy(
pendingOutputStreamStartPositionsUs,
/* srcPos= */ 1,
pendingOutputStreamStartPositionsUs,
/* destPos= */ 0,
pendingOutputStreamOffsetCount);
System.arraycopy(
pendingOutputStreamOffsetsUs, pendingOutputStreamOffsetsUs,
/* srcPos= */ 1, /* srcPos= */ 1,
pendingOutputStreamOffsetsUs, pendingOutputStreamOffsetsUs,
...@@ -1954,6 +1974,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1954,6 +1974,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
/** /**
* Returns the start position of the output {@link SampleStream}, in renderer time microseconds.
*/
protected final long getOutputStreamStartPositionUs() {
return outputStreamStartPositionUs;
}
/**
* Returns the offset that should be subtracted from {@code bufferPresentationTimeUs} in {@link * Returns the offset that should be subtracted from {@code bufferPresentationTimeUs} in {@link
* #processOutputBuffer(long, long, MediaCodec, ByteBuffer, int, int, int, long, boolean, boolean, * #processOutputBuffer(long, long, MediaCodec, ByteBuffer, int, int, int, long, boolean, boolean,
* Format)} to get the playback position with respect to the media. * Format)} to get the playback position with respect to the media.
...@@ -2088,7 +2115,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -2088,7 +2115,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
BatchBuffer batchBuffer = bypassBatchBuffer; BatchBuffer batchBuffer = bypassBatchBuffer;
// Let's process the pending buffer if any. // Let's process the pending buffer if any.
Assertions.checkState(!outputStreamEnded); checkState(!outputStreamEnded);
if (!batchBuffer.isEmpty()) { // Optimisation: Do not process buffer if empty. if (!batchBuffer.isEmpty()) { // Optimisation: Do not process buffer if empty.
if (processOutputBuffer( if (processOutputBuffer(
positionUs, positionUs,
...@@ -2127,7 +2154,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -2127,7 +2154,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
// Now refill the empty buffer for the next iteration. // Now refill the empty buffer for the next iteration.
Assertions.checkState(!inputStreamEnded); checkState(!inputStreamEnded);
FormatHolder formatHolder = getFormatHolder(); FormatHolder formatHolder = getFormatHolder();
boolean formatChange = readBatchFromSource(formatHolder, batchBuffer); boolean formatChange = readBatchFromSource(formatHolder, batchBuffer);
......
...@@ -110,7 +110,7 @@ public final class MetadataRenderer extends BaseRenderer implements Callback { ...@@ -110,7 +110,7 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
} }
@Override @Override
protected void onStreamChanged(Format[] formats, long offsetUs) { protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
decoder = decoderFactory.createDecoder(formats[0]); decoder = decoderFactory.createDecoder(formats[0]);
} }
......
...@@ -141,7 +141,7 @@ public final class TextRenderer extends BaseRenderer implements Callback { ...@@ -141,7 +141,7 @@ public final class TextRenderer extends BaseRenderer implements Callback {
} }
@Override @Override
protected void onStreamChanged(Format[] formats, long offsetUs) { protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
streamFormat = formats[0]; streamFormat = formats[0];
if (decoder != null) { if (decoder != null) {
decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM; decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM;
......
...@@ -300,12 +300,13 @@ public abstract class DecoderVideoRenderer extends BaseRenderer { ...@@ -300,12 +300,13 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
} }
@Override @Override
protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException { protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs)
throws ExoPlaybackException {
// TODO: This shouldn't just update the output stream offset as long as there are still buffers // 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 // 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. // the next stream if the playback position reached the new stream.
outputStreamOffsetUs = offsetUs; outputStreamOffsetUs = offsetUs;
super.onStreamChanged(formats, offsetUs); super.onStreamChanged(formats, startPositionUs, offsetUs);
} }
/** /**
......
...@@ -774,8 +774,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -774,8 +774,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
initialPositionUs = positionUs; initialPositionUs = positionUs;
} }
long outputStreamOffsetUs = getOutputStreamOffsetUs(); long presentationTimeUs = bufferPresentationTimeUs - getOutputStreamOffsetUs();
long presentationTimeUs = bufferPresentationTimeUs - outputStreamOffsetUs;
if (isDecodeOnlyBuffer && !isLastBuffer) { if (isDecodeOnlyBuffer && !isLastBuffer) {
skipOutputBuffer(codec, bufferIndex, presentationTimeUs); skipOutputBuffer(codec, bufferIndex, presentationTimeUs);
...@@ -803,7 +802,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -803,7 +802,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
// 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 >= getOutputStreamStartPositionUs()
&& (shouldRenderFirstFrame && (shouldRenderFirstFrame
|| (isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedSinceLastRenderUs))); || (isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedSinceLastRenderUs)));
if (forceRenderOutputBuffer) { if (forceRenderOutputBuffer) {
......
...@@ -73,7 +73,7 @@ public final class CameraMotionRenderer extends BaseRenderer { ...@@ -73,7 +73,7 @@ public final class CameraMotionRenderer extends BaseRenderer {
} }
@Override @Override
protected void onStreamChanged(Format[] formats, long offsetUs) { protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
this.offsetUs = offsetUs; this.offsetUs = offsetUs;
} }
......
...@@ -7245,7 +7245,7 @@ public final class ExoPlayerTest { ...@@ -7245,7 +7245,7 @@ public final class ExoPlayerTest {
FakeRenderer audioRenderer = FakeRenderer audioRenderer =
new FakeRenderer(C.TRACK_TYPE_AUDIO) { new FakeRenderer(C.TRACK_TYPE_AUDIO) {
@Override @Override
protected void onStreamChanged(Format[] formats, long offsetUs) protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs)
throws ExoPlaybackException { throws ExoPlaybackException {
// Fail when changing streams. This will happen during the period transition. // Fail when changing streams. This will happen during the period transition.
throw createRendererException( throw createRendererException(
...@@ -7758,7 +7758,7 @@ public final class ExoPlayerTest { ...@@ -7758,7 +7758,7 @@ public final class ExoPlayerTest {
boolean pendingFirstBufferTime = false; boolean pendingFirstBufferTime = false;
@Override @Override
protected void onStreamChanged(Format[] formats, long offsetUs) { protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
rendererStreamOffsetsUs.add(offsetUs); rendererStreamOffsetsUs.add(offsetUs);
pendingFirstBufferTime = true; pendingFirstBufferTime = true;
} }
......
...@@ -116,6 +116,7 @@ public class DecoderAudioRendererTest { ...@@ -116,6 +116,7 @@ public class DecoderAudioRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ true, /* mayRenderStartOfStream= */ true,
/* startPositionUs= */ 0,
/* offsetUs= */ 0); /* offsetUs= */ 0);
audioRenderer.setCurrentStreamFinal(); audioRenderer.setCurrentStreamFinal();
when(mockAudioSink.isEnded()).thenReturn(true); when(mockAudioSink.isEnded()).thenReturn(true);
......
...@@ -127,6 +127,7 @@ public class MediaCodecAudioRendererTest { ...@@ -127,6 +127,7 @@ public class MediaCodecAudioRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ false, /* mayRenderStartOfStream= */ false,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
mediaCodecAudioRenderer.start(); mediaCodecAudioRenderer.start();
...@@ -181,6 +182,7 @@ public class MediaCodecAudioRendererTest { ...@@ -181,6 +182,7 @@ public class MediaCodecAudioRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ false, /* mayRenderStartOfStream= */ false,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
mediaCodecAudioRenderer.start(); mediaCodecAudioRenderer.start();
...@@ -248,6 +250,7 @@ public class MediaCodecAudioRendererTest { ...@@ -248,6 +250,7 @@ public class MediaCodecAudioRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ false, /* mayRenderStartOfStream= */ false,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
exceptionThrowingRenderer.start(); exceptionThrowingRenderer.start();
......
...@@ -154,6 +154,7 @@ public class MetadataRendererTest { ...@@ -154,6 +154,7 @@ public class MetadataRendererTest {
ImmutableList.of( ImmutableList.of(
FakeSampleStreamItem.sample(/* timeUs= */ 0, /* flags= */ 0, input), FakeSampleStreamItem.sample(/* timeUs= */ 0, /* flags= */ 0, input),
FakeSampleStreamItem.END_OF_STREAM_ITEM)), FakeSampleStreamItem.END_OF_STREAM_ITEM)),
/* startPositionUs= */ 0L,
/* offsetUs= */ 0L); /* offsetUs= */ 0L);
renderer.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); // Read the format renderer.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); // Read the format
renderer.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); // Read the data renderer.render(/* positionUs= */ 0, /* elapsedRealtimeUs= */ 0); // Read the data
......
...@@ -199,6 +199,7 @@ public final class DecoderVideoRendererTest { ...@@ -199,6 +199,7 @@ public final class DecoderVideoRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ true, /* mayRenderStartOfStream= */ true,
/* startPositionUs= */ 0L,
/* offsetUs */ 0); /* offsetUs */ 0);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
renderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); renderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
...@@ -227,6 +228,7 @@ public final class DecoderVideoRendererTest { ...@@ -227,6 +228,7 @@ public final class DecoderVideoRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ false, /* mayRenderStartOfStream= */ false,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
renderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); renderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
...@@ -254,6 +256,7 @@ public final class DecoderVideoRendererTest { ...@@ -254,6 +256,7 @@ public final class DecoderVideoRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ false, /* mayRenderStartOfStream= */ false,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
renderer.start(); renderer.start();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
...@@ -268,7 +271,7 @@ public final class DecoderVideoRendererTest { ...@@ -268,7 +271,7 @@ public final class DecoderVideoRendererTest {
// TODO: Fix rendering of first frame at stream transition. // TODO: Fix rendering of first frame at stream transition.
@Ignore @Ignore
@Test @Test
public void replaceStream_whenStarted_rendersFirstFrameOfNewStream() throws Exception { public void replaceStream_rendersFirstFrameOnlyAfterStartPosition() throws Exception {
FakeSampleStream fakeSampleStream1 = FakeSampleStream fakeSampleStream1 =
new FakeSampleStream( new FakeSampleStream(
/* mediaSourceEventDispatcher= */ null, /* mediaSourceEventDispatcher= */ null,
...@@ -284,7 +287,7 @@ public final class DecoderVideoRendererTest { ...@@ -284,7 +287,7 @@ public final class DecoderVideoRendererTest {
new DrmSessionEventListener.EventDispatcher(), new DrmSessionEventListener.EventDispatcher(),
/* initialFormat= */ H264_FORMAT, /* initialFormat= */ H264_FORMAT,
ImmutableList.of( ImmutableList.of(
oneByteSample(/* timeUs= */ 0), FakeSampleStreamItem.END_OF_STREAM_ITEM)); oneByteSample(/* timeUs= */ 1_000_000), FakeSampleStreamItem.END_OF_STREAM_ITEM));
renderer.enable( renderer.enable(
RendererConfiguration.DEFAULT, RendererConfiguration.DEFAULT,
new Format[] {H264_FORMAT}, new Format[] {H264_FORMAT},
...@@ -292,67 +295,31 @@ public final class DecoderVideoRendererTest { ...@@ -292,67 +295,31 @@ public final class DecoderVideoRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ true, /* mayRenderStartOfStream= */ true,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
renderer.start();
boolean replacedStream = false; boolean replacedStream = false;
for (int i = 0; i <= 10; i++) { // Render until just before the start position of the second stream
renderer.render(/* positionUs= */ i * 10, SystemClock.elapsedRealtime() * 1000); for (int i = 0; i < 5; i++) {
if (!replacedStream && renderer.hasReadStreamToEnd()) {
renderer.replaceStream(new Format[] {H264_FORMAT}, fakeSampleStream2, /* offsetUs= */ 100);
replacedStream = true;
}
// Ensure pending messages are delivered.
ShadowLooper.idleMainLooper();
}
verify(eventListener, times(2)).onRenderedFirstFrame(any());
}
// TODO: Fix rendering of first frame at stream transition.
@Ignore
@Test
public void replaceStream_whenNotStarted_doesNotRenderFirstFrameOfNewStream() throws Exception {
FakeSampleStream fakeSampleStream1 =
new FakeSampleStream(
/* mediaSourceEventDispatcher= */ null,
DrmSessionManager.DUMMY,
new DrmSessionEventListener.EventDispatcher(),
/* initialFormat= */ H264_FORMAT,
ImmutableList.of(
oneByteSample(/* timeUs= */ 0), FakeSampleStreamItem.END_OF_STREAM_ITEM));
FakeSampleStream fakeSampleStream2 =
new FakeSampleStream(
/* mediaSourceEventDispatcher= */ null,
DrmSessionManager.DUMMY,
new DrmSessionEventListener.EventDispatcher(),
/* initialFormat= */ H264_FORMAT,
ImmutableList.of(
oneByteSample(/* timeUs= */ 0), FakeSampleStreamItem.END_OF_STREAM_ITEM));
renderer.enable(
RendererConfiguration.DEFAULT,
new Format[] {H264_FORMAT},
fakeSampleStream1,
/* positionUs= */ 0,
/* joining= */ false,
/* mayRenderStartOfStream= */ true,
/* offsetUs */ 0);
boolean replacedStream = false;
for (int i = 0; i < 10; i++) {
renderer.render(/* positionUs= */ i * 10, SystemClock.elapsedRealtime() * 1000); renderer.render(/* positionUs= */ i * 10, SystemClock.elapsedRealtime() * 1000);
if (!replacedStream && renderer.hasReadStreamToEnd()) { if (!replacedStream && renderer.hasReadStreamToEnd()) {
renderer.replaceStream(new Format[] {H264_FORMAT}, fakeSampleStream2, /* offsetUs= */ 100); renderer.replaceStream(
new Format[] {H264_FORMAT},
fakeSampleStream2,
/* startPositionUs= */ 50,
/* offsetUs= */ 100);
replacedStream = true; replacedStream = true;
} }
// Ensure pending messages are delivered. // Ensure pending messages are delivered.
ShadowLooper.idleMainLooper(); ShadowLooper.idleMainLooper();
} }
// Expect only the first frame of the first stream to have been rendered.
verify(eventListener).onRenderedFirstFrame(any()); verify(eventListener).onRenderedFirstFrame(any());
// Render to streamOffsetUs and verify the new first frame gets rendered. // Render to the start position of the stream and verify the new first frame gets rendered (even
renderer.render(/* positionUs= */ 100, SystemClock.elapsedRealtime() * 1000); // though its sampleTimeUs is far in the future).
renderer.render(/* positionUs= */ 50, SystemClock.elapsedRealtime() * 1000);
verify(eventListener, times(2)).onRenderedFirstFrame(any()); verify(eventListener, times(2)).onRenderedFirstFrame(any());
} }
......
...@@ -140,6 +140,7 @@ public class MediaCodecVideoRendererTest { ...@@ -140,6 +140,7 @@ public class MediaCodecVideoRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ true, /* mayRenderStartOfStream= */ true,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
mediaCodecVideoRenderer.start(); mediaCodecVideoRenderer.start();
...@@ -171,6 +172,7 @@ public class MediaCodecVideoRendererTest { ...@@ -171,6 +172,7 @@ public class MediaCodecVideoRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ true, /* mayRenderStartOfStream= */ true,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
mediaCodecVideoRenderer.setCurrentStreamFinal(); mediaCodecVideoRenderer.setCurrentStreamFinal();
mediaCodecVideoRenderer.start(); mediaCodecVideoRenderer.start();
...@@ -212,6 +214,7 @@ public class MediaCodecVideoRendererTest { ...@@ -212,6 +214,7 @@ public class MediaCodecVideoRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ false, /* mayRenderStartOfStream= */ false,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
mediaCodecVideoRenderer.start(); mediaCodecVideoRenderer.start();
mediaCodecVideoRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); mediaCodecVideoRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
...@@ -256,6 +259,7 @@ public class MediaCodecVideoRendererTest { ...@@ -256,6 +259,7 @@ public class MediaCodecVideoRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ true, /* mayRenderStartOfStream= */ true,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
mediaCodecVideoRenderer.start(); mediaCodecVideoRenderer.start();
...@@ -291,6 +295,7 @@ public class MediaCodecVideoRendererTest { ...@@ -291,6 +295,7 @@ public class MediaCodecVideoRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ true, /* mayRenderStartOfStream= */ true,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
mediaCodecVideoRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); mediaCodecVideoRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
...@@ -317,6 +322,7 @@ public class MediaCodecVideoRendererTest { ...@@ -317,6 +322,7 @@ public class MediaCodecVideoRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ false, /* mayRenderStartOfStream= */ false,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
mediaCodecVideoRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000); mediaCodecVideoRenderer.render(/* positionUs= */ 0, SystemClock.elapsedRealtime() * 1000);
...@@ -342,6 +348,7 @@ public class MediaCodecVideoRendererTest { ...@@ -342,6 +348,7 @@ public class MediaCodecVideoRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ false, /* mayRenderStartOfStream= */ false,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
mediaCodecVideoRenderer.start(); mediaCodecVideoRenderer.start();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
...@@ -352,7 +359,7 @@ public class MediaCodecVideoRendererTest { ...@@ -352,7 +359,7 @@ public class MediaCodecVideoRendererTest {
} }
@Test @Test
public void replaceStream_whenStarted_rendersFirstFrameOfNewStream() throws Exception { public void replaceStream_rendersFirstFrameOnlyAfterStartPosition() throws Exception {
FakeSampleStream fakeSampleStream1 = FakeSampleStream fakeSampleStream1 =
new FakeSampleStream( new FakeSampleStream(
/* mediaSourceEventDispatcher= */ null, /* mediaSourceEventDispatcher= */ null,
...@@ -369,7 +376,7 @@ public class MediaCodecVideoRendererTest { ...@@ -369,7 +376,7 @@ public class MediaCodecVideoRendererTest {
new DrmSessionEventListener.EventDispatcher(), new DrmSessionEventListener.EventDispatcher(),
/* initialFormat= */ VIDEO_H264, /* initialFormat= */ VIDEO_H264,
ImmutableList.of( ImmutableList.of(
oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), oneByteSample(/* timeUs= */ 1_000_000, C.BUFFER_FLAG_KEY_FRAME),
FakeSampleStreamItem.END_OF_STREAM_ITEM)); FakeSampleStreamItem.END_OF_STREAM_ITEM));
mediaCodecVideoRenderer.enable( mediaCodecVideoRenderer.enable(
RendererConfiguration.DEFAULT, RendererConfiguration.DEFAULT,
...@@ -378,67 +385,30 @@ public class MediaCodecVideoRendererTest { ...@@ -378,67 +385,30 @@ public class MediaCodecVideoRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ true, /* mayRenderStartOfStream= */ true,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
mediaCodecVideoRenderer.start();
boolean replacedStream = false; boolean replacedStream = false;
for (int i = 0; i <= 10; i++) { // Render until just before the start position of the second stream
mediaCodecVideoRenderer.render( for (int i = 0; i < 5; i++) {
/* positionUs= */ i * 10, SystemClock.elapsedRealtime() * 1000);
if (!replacedStream && mediaCodecVideoRenderer.hasReadStreamToEnd()) {
mediaCodecVideoRenderer.replaceStream(
new Format[] {VIDEO_H264}, fakeSampleStream2, /* offsetUs= */ 100);
replacedStream = true;
}
}
verify(eventListener, times(2)).onRenderedFirstFrame(any());
}
@Test
public void replaceStream_whenNotStarted_doesNotRenderFirstFrameOfNewStream() throws Exception {
FakeSampleStream fakeSampleStream1 =
new FakeSampleStream(
/* mediaSourceEventDispatcher= */ null,
DrmSessionManager.DUMMY,
new DrmSessionEventListener.EventDispatcher(),
/* initialFormat= */ VIDEO_H264,
ImmutableList.of(
oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME),
FakeSampleStreamItem.END_OF_STREAM_ITEM));
FakeSampleStream fakeSampleStream2 =
new FakeSampleStream(
/* mediaSourceEventDispatcher= */ null,
DrmSessionManager.DUMMY,
new DrmSessionEventListener.EventDispatcher(),
/* initialFormat= */ VIDEO_H264,
ImmutableList.of(
oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME),
FakeSampleStreamItem.END_OF_STREAM_ITEM));
mediaCodecVideoRenderer.enable(
RendererConfiguration.DEFAULT,
new Format[] {VIDEO_H264},
fakeSampleStream1,
/* positionUs= */ 0,
/* joining= */ false,
/* mayRenderStartOfStream= */ true,
/* offsetUs */ 0);
boolean replacedStream = false;
for (int i = 0; i < 10; i++) {
mediaCodecVideoRenderer.render( mediaCodecVideoRenderer.render(
/* positionUs= */ i * 10, SystemClock.elapsedRealtime() * 1000); /* positionUs= */ i * 10, SystemClock.elapsedRealtime() * 1000);
if (!replacedStream && mediaCodecVideoRenderer.hasReadStreamToEnd()) { if (!replacedStream && mediaCodecVideoRenderer.hasReadStreamToEnd()) {
mediaCodecVideoRenderer.replaceStream( mediaCodecVideoRenderer.replaceStream(
new Format[] {VIDEO_H264}, fakeSampleStream2, /* offsetUs= */ 100); new Format[] {VIDEO_H264},
fakeSampleStream2,
/* startPositionUs= */ 50,
/* offsetUs= */ 100);
replacedStream = true; replacedStream = true;
} }
} }
// Expect only the first frame of the first stream to have been rendered.
verify(eventListener).onRenderedFirstFrame(any()); verify(eventListener).onRenderedFirstFrame(any());
// Render to streamOffsetUs and verify the new first frame gets rendered. // Render to the start position of the stream and verify the new first frame gets rendered (even
mediaCodecVideoRenderer.render(/* positionUs= */ 100, SystemClock.elapsedRealtime() * 1000); // though its sampleTimeUs is far in the future).
mediaCodecVideoRenderer.render(/* positionUs= */ 50, SystemClock.elapsedRealtime() * 1000);
verify(eventListener, times(2)).onRenderedFirstFrame(any()); verify(eventListener, times(2)).onRenderedFirstFrame(any());
} }
...@@ -473,6 +443,7 @@ public class MediaCodecVideoRendererTest { ...@@ -473,6 +443,7 @@ public class MediaCodecVideoRendererTest {
/* positionUs= */ 0, /* positionUs= */ 0,
/* joining= */ false, /* joining= */ false,
/* mayRenderStartOfStream= */ true, /* mayRenderStartOfStream= */ true,
/* startPositionUs= */ 0,
/* offsetUs */ 0); /* offsetUs */ 0);
mediaCodecVideoRenderer.setCurrentStreamFinal(); mediaCodecVideoRenderer.setCurrentStreamFinal();
......
...@@ -33,7 +33,7 @@ public class FakeVideoRenderer extends FakeRenderer { ...@@ -33,7 +33,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;
private long streamOffsetUs; private long startPositionUs;
private boolean renderedFirstFrameAfterReset; private boolean renderedFirstFrameAfterReset;
private boolean mayRenderFirstFrameAfterEnableIfNotStarted; private boolean mayRenderFirstFrameAfterEnableIfNotStarted;
private boolean renderedFirstFrameAfterEnable; private boolean renderedFirstFrameAfterEnable;
...@@ -54,9 +54,10 @@ public class FakeVideoRenderer extends FakeRenderer { ...@@ -54,9 +54,10 @@ public class FakeVideoRenderer extends FakeRenderer {
} }
@Override @Override
protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException { protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs)
super.onStreamChanged(formats, offsetUs); throws ExoPlaybackException {
streamOffsetUs = offsetUs; super.onStreamChanged(formats, startPositionUs, offsetUs);
this.startPositionUs = startPositionUs;
if (renderedFirstFrameAfterReset) { if (renderedFirstFrameAfterReset) {
renderedFirstFrameAfterReset = false; renderedFirstFrameAfterReset = false;
} }
...@@ -101,7 +102,7 @@ public class FakeVideoRenderer extends FakeRenderer { ...@@ -101,7 +102,7 @@ public class FakeVideoRenderer extends FakeRenderer {
!renderedFirstFrameAfterEnable !renderedFirstFrameAfterEnable
? (getState() == Renderer.STATE_STARTED || mayRenderFirstFrameAfterEnableIfNotStarted) ? (getState() == Renderer.STATE_STARTED || mayRenderFirstFrameAfterEnableIfNotStarted)
: !renderedFirstFrameAfterReset; : !renderedFirstFrameAfterReset;
shouldProcess |= shouldRenderFirstFrame && playbackPositionUs >= streamOffsetUs; shouldProcess |= shouldRenderFirstFrame && playbackPositionUs >= startPositionUs;
if (shouldProcess && !renderedFirstFrameAfterReset) { if (shouldProcess && !renderedFirstFrameAfterReset) {
@MonotonicNonNull Format format = Assertions.checkNotNull(this.format); @MonotonicNonNull Format format = Assertions.checkNotNull(this.format);
eventDispatcher.videoSizeChanged( eventDispatcher.videoSizeChanged(
......
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