mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
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
This commit is contained in:
parent
5c4b8085a0
commit
bf5e6c7862
18 changed files with 133 additions and 147 deletions
|
|
@ -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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
@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 {
|
||||||
* <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 {
|
||||||
* 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;
|
||||||
// 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;
|
||||||
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
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
* @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 {
|
||||||
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 {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 {
|
||||||
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 {
|
||||||
* <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 @@
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
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 {
|
||||||
@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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
} 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 {
|
||||||
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 {
|
||||||
@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,8 +1575,15 @@ 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(
|
||||||
|
pendingOutputStreamStartPositionsUs,
|
||||||
|
/* srcPos= */ 1,
|
||||||
|
pendingOutputStreamStartPositionsUs,
|
||||||
|
/* destPos= */ 0,
|
||||||
|
pendingOutputStreamOffsetCount);
|
||||||
System.arraycopy(
|
System.arraycopy(
|
||||||
pendingOutputStreamOffsetsUs,
|
pendingOutputStreamOffsetsUs,
|
||||||
/* srcPos= */ 1,
|
/* srcPos= */ 1,
|
||||||
|
|
@ -1953,6 +1973,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||||
return largestQueuedPresentationTimeUs;
|
return largestQueuedPresentationTimeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
|
@ -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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
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 {
|
||||||
// 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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
/* 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 {
|
||||||
/* 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 {
|
||||||
/* 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 {
|
||||||
/* 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 {
|
||||||
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 {
|
||||||
/* 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 {
|
||||||
/* 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 {
|
||||||
/* 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 {
|
||||||
// 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 {
|
||||||
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 {
|
||||||
/* 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
|
||||||
|
for (int i = 0; i < 5; 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(
|
||||||
replacedStream = true;
|
new Format[] {H264_FORMAT},
|
||||||
}
|
fakeSampleStream2,
|
||||||
// Ensure pending messages are delivered.
|
/* startPositionUs= */ 50,
|
||||||
ShadowLooper.idleMainLooper();
|
/* offsetUs= */ 100);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
if (!replacedStream && renderer.hasReadStreamToEnd()) {
|
|
||||||
renderer.replaceStream(new Format[] {H264_FORMAT}, fakeSampleStream2, /* 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 {
|
||||||
/* 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 {
|
||||||
/* 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 {
|
||||||
/* 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 {
|
||||||
/* 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 {
|
||||||
/* 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 {
|
||||||
/* 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 {
|
||||||
/* 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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
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 {
|
||||||
/* 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
|
||||||
|
for (int i = 0; i < 5; 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},
|
||||||
replacedStream = true;
|
fakeSampleStream2,
|
||||||
}
|
/* startPositionUs= */ 50,
|
||||||
}
|
/* offsetUs= */ 100);
|
||||||
|
|
||||||
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(
|
|
||||||
/* positionUs= */ i * 10, SystemClock.elapsedRealtime() * 1000);
|
|
||||||
if (!replacedStream && mediaCodecVideoRenderer.hasReadStreamToEnd()) {
|
|
||||||
mediaCodecVideoRenderer.replaceStream(
|
|
||||||
new Format[] {VIDEO_H264}, fakeSampleStream2, /* 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 {
|
||||||
/* 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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
!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(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue