Correctly handle a SampleStream ending without providing a format

I'm going to introduce an EmptySampleStream that will be used in
some cases in conjunction as part of 608/EMSG support. This change
avoids EmptySampleStream having to provide a dummy format.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=148383831
This commit is contained in:
olly 2017-02-23 13:30:19 -08:00 committed by Oliver Woodman
parent ef2541e654
commit d99cb28e6a
15 changed files with 155 additions and 104 deletions

View File

@ -65,6 +65,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
private final boolean playClearSamplesWithoutKeys; private final boolean playClearSamplesWithoutKeys;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final FormatHolder formatHolder; private final FormatHolder formatHolder;
private final DecoderInputBuffer flagsOnlyBuffer;
private final DrmSessionManager<ExoMediaCrypto> drmSessionManager; private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
private DecoderCounters decoderCounters; private DecoderCounters decoderCounters;
@ -149,6 +150,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
joiningDeadlineMs = -1; joiningDeadlineMs = -1;
clearLastReportedVideoSize(); clearLastReportedVideoSize();
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
flagsOnlyBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
outputMode = VpxDecoder.OUTPUT_MODE_NONE; outputMode = VpxDecoder.OUTPUT_MODE_NONE;
} }
@ -165,10 +167,22 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
return; return;
} }
// Try and read a format if we don't have one already. if (format == null) {
if (format == null && !readFormat()) { // We don't have a format yet, so try and read one.
// We can't make progress without one. flagsOnlyBuffer.clear();
return; int result = readSource(formatHolder, flagsOnlyBuffer, true);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format);
} else if (result == C.RESULT_BUFFER_READ) {
// End of stream read having not read a format.
Assertions.checkState(flagsOnlyBuffer.isEndOfStream());
inputStreamEnded = true;
outputStreamEnded = true;
return;
} else {
// We still don't have a format and can't make progress without one.
return;
}
} }
if (isRendererAvailable()) { if (isRendererAvailable()) {
@ -327,7 +341,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
// We've already read an encrypted sample into buffer, and are waiting for keys. // We've already read an encrypted sample into buffer, and are waiting for keys.
result = C.RESULT_BUFFER_READ; result = C.RESULT_BUFFER_READ;
} else { } else {
result = readSource(formatHolder, inputBuffer); result = readSource(formatHolder, inputBuffer, false);
} }
if (result == C.RESULT_NOTHING_READ) { if (result == C.RESULT_NOTHING_READ) {
@ -485,15 +499,6 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
} }
} }
private boolean readFormat() throws ExoPlaybackException {
int result = readSource(formatHolder, null);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format);
return true;
}
return false;
}
private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
Format oldFormat = format; Format oldFormat = format;
format = newFormat; format = newFormat;

View File

@ -513,8 +513,9 @@ public final class ExoPlayerTest extends TestCase {
} }
@Override @Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
if (buffer == null || !readFormat) { boolean formatRequired) {
if (formatRequired || !readFormat) {
formatHolder.format = format; formatHolder.format = format;
readFormat = true; readFormat = true;
return C.RESULT_FORMAT_READ; return C.RESULT_FORMAT_READ;
@ -571,7 +572,7 @@ public final class ExoPlayerTest extends TestCase {
FormatHolder formatHolder = new FormatHolder(); FormatHolder formatHolder = new FormatHolder();
DecoderInputBuffer buffer = DecoderInputBuffer buffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
int result = readSource(formatHolder, buffer); int result = readSource(formatHolder, buffer, false);
if (result == C.RESULT_FORMAT_READ) { if (result == C.RESULT_FORMAT_READ) {
formatReadCount++; formatReadCount++;
assertEquals(expectedFormat, formatHolder.format); assertEquals(expectedFormat, formatHolder.format);

View File

@ -262,13 +262,16 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
* @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
* @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
* end of the stream. If the end of the stream has been reached, the * end of the stream. If the end of the stream has been reached, the
* {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. May be null if the * {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer.
* caller requires that the format of the stream be read even if it's not changing. * @param formatRequired Whether the caller requires that the format of the stream be read even if
* it's not changing. A sample will never be read if set to true, however it is still possible
* for the end of stream or nothing to be read.
* @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or
* {@link C#RESULT_BUFFER_READ}. * {@link C#RESULT_BUFFER_READ}.
*/ */
protected final int readSource(FormatHolder formatHolder, DecoderInputBuffer buffer) { protected final int readSource(FormatHolder formatHolder, DecoderInputBuffer buffer,
int result = stream.readData(formatHolder, buffer); boolean formatRequired) {
int result = stream.readData(formatHolder, buffer, formatRequired);
if (result == C.RESULT_BUFFER_READ) { if (result == C.RESULT_BUFFER_READ) {
if (buffer.isEndOfStream()) { if (buffer.isEndOfStream()) {
readEndOfStream = true; readEndOfStream = true;

View File

@ -34,6 +34,7 @@ import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MediaClock; import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.TraceUtil;
@ -67,12 +68,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
*/ */
private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2; private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2;
private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
private final boolean playClearSamplesWithoutKeys; private final boolean playClearSamplesWithoutKeys;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final AudioTrack audioTrack; private final AudioTrack audioTrack;
private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
private final FormatHolder formatHolder; private final FormatHolder formatHolder;
private final DecoderInputBuffer flagsOnlyBuffer;
private DecoderCounters decoderCounters; private DecoderCounters decoderCounters;
private Format inputFormat; private Format inputFormat;
@ -142,11 +143,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys, DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys,
BufferProcessor... bufferProcessors) { BufferProcessor... bufferProcessors) {
super(C.TRACK_TYPE_AUDIO); super(C.TRACK_TYPE_AUDIO);
this.drmSessionManager = drmSessionManager;
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
audioTrack = new AudioTrack(audioCapabilities, bufferProcessors, new AudioTrackListener()); audioTrack = new AudioTrack(audioCapabilities, bufferProcessors, new AudioTrackListener());
this.drmSessionManager = drmSessionManager;
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; flagsOnlyBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
decoderReinitializationState = REINITIALIZATION_STATE_NONE; decoderReinitializationState = REINITIALIZATION_STATE_NONE;
audioTrackNeedsConfigure = true; audioTrackNeedsConfigure = true;
} }
@ -187,9 +189,22 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
// Try and read a format if we don't have one already. // Try and read a format if we don't have one already.
if (inputFormat == null && !readFormat()) { if (inputFormat == null) {
// We can't make progress without one. // We don't have a format yet, so try and read one.
return; flagsOnlyBuffer.clear();
int result = readSource(formatHolder, flagsOnlyBuffer, true);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format);
} else if (result == C.RESULT_BUFFER_READ) {
// End of stream read having not read a format.
Assertions.checkState(flagsOnlyBuffer.isEndOfStream());
inputStreamEnded = true;
processEndOfStream();
return;
} else {
// We still don't have a format and can't make progress without one.
return;
}
} }
// If we don't have a decoder yet, we need to instantiate one. // If we don't have a decoder yet, we need to instantiate one.
@ -284,8 +299,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} else { } else {
outputBuffer.release(); outputBuffer.release();
outputBuffer = null; outputBuffer = null;
outputStreamEnded = true; processEndOfStream();
audioTrack.playToEndOfStream();
} }
return false; return false;
} }
@ -334,7 +348,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
// We've already read an encrypted sample into buffer, and are waiting for keys. // We've already read an encrypted sample into buffer, and are waiting for keys.
result = C.RESULT_BUFFER_READ; result = C.RESULT_BUFFER_READ;
} else { } else {
result = readSource(formatHolder, inputBuffer); result = readSource(formatHolder, inputBuffer, false);
} }
if (result == C.RESULT_NOTHING_READ) { if (result == C.RESULT_NOTHING_READ) {
@ -375,6 +389,15 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
&& (bufferEncrypted || !playClearSamplesWithoutKeys); && (bufferEncrypted || !playClearSamplesWithoutKeys);
} }
private void processEndOfStream() throws ExoPlaybackException {
outputStreamEnded = true;
try {
audioTrack.playToEndOfStream();
} catch (AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
}
}
private void flushDecoder() throws ExoPlaybackException { private void flushDecoder() throws ExoPlaybackException {
waitingForKeys = false; waitingForKeys = false;
if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) { if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) {
@ -523,15 +546,6 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
decoderReceivedBuffers = false; decoderReceivedBuffers = false;
} }
private boolean readFormat() throws ExoPlaybackException {
int result = readSource(formatHolder, null);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format);
return true;
}
return false;
}
private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
Format oldFormat = inputFormat; Format oldFormat = inputFormat;
inputFormat = newFormat; inputFormat = newFormat;

View File

@ -267,40 +267,42 @@ public final class DefaultTrackOutput implements TrackOutput {
* @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
* @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
* end of the stream. If the end of the stream has been reached, the * end of the stream. If the end of the stream has been reached, the
* {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. May be null if the * {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer.
* caller requires that the format of the stream be read even if it's not changing. * @param formatRequired Whether the caller requires that the format of the stream be read even if
* it's not changing. A sample will never be read if set to true, however it is still possible
* for the end of stream or nothing to be read.
* @param loadingFinished True if an empty queue should be considered the end of the stream. * @param loadingFinished True if an empty queue should be considered the end of the stream.
* @param decodeOnlyUntilUs If a buffer is read, the {@link C#BUFFER_FLAG_DECODE_ONLY} flag will * @param decodeOnlyUntilUs If a buffer is read, the {@link C#BUFFER_FLAG_DECODE_ONLY} flag will
* be set if the buffer's timestamp is less than this value. * be set if the buffer's timestamp is less than this value.
* @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or
* {@link C#RESULT_BUFFER_READ}. * {@link C#RESULT_BUFFER_READ}.
*/ */
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean loadingFinished, public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired,
long decodeOnlyUntilUs) { boolean loadingFinished, long decodeOnlyUntilUs) {
switch (infoQueue.readData(formatHolder, buffer, downstreamFormat, extrasHolder)) { int result = infoQueue.readData(formatHolder, buffer, formatRequired, loadingFinished,
case C.RESULT_NOTHING_READ: downstreamFormat, extrasHolder);
if (loadingFinished) { switch (result) {
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
}
return C.RESULT_NOTHING_READ;
case C.RESULT_FORMAT_READ: case C.RESULT_FORMAT_READ:
downstreamFormat = formatHolder.format; downstreamFormat = formatHolder.format;
return C.RESULT_FORMAT_READ; return C.RESULT_FORMAT_READ;
case C.RESULT_BUFFER_READ: case C.RESULT_BUFFER_READ:
if (buffer.timeUs < decodeOnlyUntilUs) { if (!buffer.isEndOfStream()) {
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); if (buffer.timeUs < decodeOnlyUntilUs) {
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
}
// Read encryption data if the sample is encrypted.
if (buffer.isEncrypted()) {
readEncryptionData(buffer, extrasHolder);
}
// Write the sample data into the holder.
buffer.ensureSpaceForWrite(extrasHolder.size);
readData(extrasHolder.offset, buffer.data, extrasHolder.size);
// Advance the read head.
dropDownstreamTo(extrasHolder.nextOffset);
} }
// Read encryption data if the sample is encrypted.
if (buffer.isEncrypted()) {
readEncryptionData(buffer, extrasHolder);
}
// Write the sample data into the holder.
buffer.ensureSpaceForWrite(extrasHolder.size);
readData(extrasHolder.offset, buffer.data, extrasHolder.size);
// Advance the read head.
dropDownstreamTo(extrasHolder.nextOffset);
return C.RESULT_BUFFER_READ; return C.RESULT_BUFFER_READ;
case C.RESULT_NOTHING_READ:
return C.RESULT_NOTHING_READ;
default: default:
throw new IllegalStateException(); throw new IllegalStateException();
} }
@ -760,23 +762,34 @@ public final class DefaultTrackOutput implements TrackOutput {
* and the absolute position of the first byte that may still be required after the current * and the absolute position of the first byte that may still be required after the current
* sample has been read. May be null if the caller requires that the format of the stream be * sample has been read. May be null if the caller requires that the format of the stream be
* read even if it's not changing. * read even if it's not changing.
* @param formatRequired Whether the caller requires that the format of the stream be read even
* if it's not changing. A sample will never be read if set to true, however it is still
* possible for the end of stream or nothing to be read.
* @param loadingFinished True if an empty queue should be considered the end of the stream.
* @param downstreamFormat The current downstream {@link Format}. If the format of the next * @param downstreamFormat The current downstream {@link Format}. If the format of the next
* sample is different to the current downstream format then a format will be read. * sample is different to the current downstream format then a format will be read.
* @param extrasHolder The holder into which extra sample information should be written. * @param extrasHolder The holder into which extra sample information should be written.
* @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ}
* or {@link C#RESULT_BUFFER_READ}. * or {@link C#RESULT_BUFFER_READ}.
*/ */
@SuppressWarnings("ReferenceEquality")
public synchronized int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, public synchronized int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
Format downstreamFormat, BufferExtrasHolder extrasHolder) { boolean formatRequired, boolean loadingFinished, Format downstreamFormat,
BufferExtrasHolder extrasHolder) {
if (queueSize == 0) { if (queueSize == 0) {
if (upstreamFormat != null && (buffer == null || upstreamFormat != downstreamFormat)) { if (loadingFinished) {
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
} else if (upstreamFormat != null
&& (formatRequired || upstreamFormat != downstreamFormat)) {
formatHolder.format = upstreamFormat; formatHolder.format = upstreamFormat;
return C.RESULT_FORMAT_READ; return C.RESULT_FORMAT_READ;
} else {
return C.RESULT_NOTHING_READ;
} }
return C.RESULT_NOTHING_READ;
} }
if (buffer == null || formats[relativeReadIndex] != downstreamFormat) { if (formatRequired || formats[relativeReadIndex] != downstreamFormat) {
formatHolder.format = formats[relativeReadIndex]; formatHolder.format = formats[relativeReadIndex];
return C.RESULT_FORMAT_READ; return C.RESULT_FORMAT_READ;
} }

View File

@ -484,7 +484,21 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return; return;
} }
if (format == null) { if (format == null) {
readFormat(); // We don't have a format yet, so try and read one.
buffer.clear();
int result = readSource(formatHolder, buffer, true);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format);
} else if (result == C.RESULT_BUFFER_READ) {
// End of stream read having not read a format.
Assertions.checkState(buffer.isEndOfStream());
inputStreamEnded = true;
processEndOfStream();
return;
} else {
// We still don't have a format and can't make progress without one.
return;
}
} }
maybeInitCodec(); maybeInitCodec();
if (codec != null) { if (codec != null) {
@ -498,13 +512,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
decoderCounters.ensureUpdated(); decoderCounters.ensureUpdated();
} }
private void readFormat() throws ExoPlaybackException {
int result = readSource(formatHolder, null);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format);
}
}
protected void flushCodec() throws ExoPlaybackException { protected void flushCodec() throws ExoPlaybackException {
codecHotswapDeadlineMs = C.TIME_UNSET; codecHotswapDeadlineMs = C.TIME_UNSET;
inputIndex = C.INDEX_UNSET; inputIndex = C.INDEX_UNSET;
@ -594,7 +601,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecReconfigurationState = RECONFIGURATION_STATE_QUEUE_PENDING; codecReconfigurationState = RECONFIGURATION_STATE_QUEUE_PENDING;
} }
adaptiveReconfigurationBytes = buffer.data.position(); adaptiveReconfigurationBytes = buffer.data.position();
result = readSource(formatHolder, buffer); result = readSource(formatHolder, buffer, false);
} }
if (result == C.RESULT_NOTHING_READ) { if (result == C.RESULT_NOTHING_READ) {

View File

@ -109,7 +109,7 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (!inputStreamEnded && pendingMetadata == null) { if (!inputStreamEnded && pendingMetadata == null) {
buffer.clear(); buffer.clear();
int result = readSource(formatHolder, buffer); int result = readSource(formatHolder, buffer, false);
if (result == C.RESULT_BUFFER_READ) { if (result == C.RESULT_BUFFER_READ) {
if (buffer.isEndOfStream()) { if (buffer.isEndOfStream()) {
inputStreamEnded = true; inputStreamEnded = true;

View File

@ -231,18 +231,16 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
} }
@Override @Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean requireFormat) {
if (pendingDiscontinuity) { if (pendingDiscontinuity) {
return C.RESULT_NOTHING_READ; return C.RESULT_NOTHING_READ;
} }
if (buffer == null) {
return stream.readData(formatHolder, null);
}
if (sentEos) { if (sentEos) {
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ; return C.RESULT_BUFFER_READ;
} }
int result = stream.readData(formatHolder, buffer); int result = stream.readData(formatHolder, buffer, requireFormat);
// TODO: Clear gapless playback metadata if a format was read (if applicable). // TODO: Clear gapless playback metadata if a format was read (if applicable).
if (endUs != C.TIME_END_OF_SOURCE && ((result == C.RESULT_BUFFER_READ if (endUs != C.TIME_END_OF_SOURCE && ((result == C.RESULT_BUFFER_READ
&& buffer.timeUs >= endUs) || (result == C.RESULT_NOTHING_READ && buffer.timeUs >= endUs) || (result == C.RESULT_NOTHING_READ

View File

@ -325,13 +325,14 @@ import java.io.IOException;
loader.maybeThrowError(); loader.maybeThrowError();
} }
/* package */ int readData(int track, FormatHolder formatHolder, DecoderInputBuffer buffer) { /* package */ int readData(int track, FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean formatRequired) {
if (notifyReset || isPendingReset()) { if (notifyReset || isPendingReset()) {
return C.RESULT_NOTHING_READ; return C.RESULT_NOTHING_READ;
} }
return sampleQueues.valueAt(track).readData(formatHolder, buffer, loadingFinished, return sampleQueues.valueAt(track).readData(formatHolder, buffer, formatRequired,
lastSeekPositionUs); loadingFinished, lastSeekPositionUs);
} }
// Loader.Callback implementation. // Loader.Callback implementation.
@ -552,8 +553,9 @@ import java.io.IOException;
} }
@Override @Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
return ExtractorMediaPeriod.this.readData(track, formatHolder, buffer); boolean formatRequired) {
return ExtractorMediaPeriod.this.readData(track, formatHolder, buffer, formatRequired);
} }
@Override @Override

View File

@ -45,20 +45,24 @@ public interface SampleStream {
/** /**
* Attempts to read from the stream. * Attempts to read from the stream.
* <p> * <p>
* If no data is available then {@link C#RESULT_NOTHING_READ} is returned. If the format of the * If the stream has ended then {@link C#BUFFER_FLAG_END_OF_STREAM} flag is set on {@code buffer}
* media is changing or if {@code buffer == null} then {@code formatHolder} is populated and * and {@link C#RESULT_BUFFER_READ} is returned. Else if no data is available then
* {@link C#RESULT_NOTHING_READ} is returned. Else if the format of the media is changing or if
* {@code formatRequired} is set then {@code formatHolder} is populated and
* {@link C#RESULT_FORMAT_READ} is returned. Else {@code buffer} is populated and * {@link C#RESULT_FORMAT_READ} is returned. Else {@code buffer} is populated and
* {@link C#RESULT_BUFFER_READ} is returned. * {@link C#RESULT_BUFFER_READ} is returned.
* *
* @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
* @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
* end of the stream. If the end of the stream has been reached, the * end of the stream. If the end of the stream has been reached, the
* {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. May be null if the * {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer.
* caller requires that the format of the stream be read even if it's not changing. * @param formatRequired Whether the caller requires that the format of the stream be read even if
* it's not changing. A sample will never be read if set to true, however it is still possible
* for the end of stream or nothing to be read.
* @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or
* {@link C#RESULT_BUFFER_READ}. * {@link C#RESULT_BUFFER_READ}.
*/ */
int readData(FormatHolder formatHolder, DecoderInputBuffer buffer); int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired);
/** /**
* Attempts to skip to the keyframe before the specified time. * Attempts to skip to the keyframe before the specified time.

View File

@ -205,14 +205,15 @@ import java.util.Arrays;
} }
@Override @Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
if (buffer == null || streamState == STREAM_STATE_SEND_FORMAT) { boolean requireFormat) {
if (streamState == STREAM_STATE_END_OF_STREAM) {
buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
} else if (requireFormat || streamState == STREAM_STATE_SEND_FORMAT) {
formatHolder.format = format; formatHolder.format = format;
streamState = STREAM_STATE_SEND_SAMPLE; streamState = STREAM_STATE_SEND_SAMPLE;
return C.RESULT_FORMAT_READ; return C.RESULT_FORMAT_READ;
} else if (streamState == STREAM_STATE_END_OF_STREAM) {
buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
} }
Assertions.checkState(streamState == STREAM_STATE_SEND_SAMPLE); Assertions.checkState(streamState == STREAM_STATE_SEND_SAMPLE);

View File

@ -169,7 +169,8 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
} }
@Override @Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean formatRequired) {
if (isPendingReset()) { if (isPendingReset()) {
return C.RESULT_NOTHING_READ; return C.RESULT_NOTHING_READ;
} }
@ -187,7 +188,8 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
currentChunk.startTimeUs); currentChunk.startTimeUs);
} }
downstreamTrackFormat = trackFormat; downstreamTrackFormat = trackFormat;
return sampleQueue.readData(formatHolder, buffer, loadingFinished, lastSeekPositionUs); return sampleQueue.readData(formatHolder, buffer, formatRequired, loadingFinished,
lastSeekPositionUs);
} }
@Override @Override

View File

@ -45,8 +45,8 @@ import java.io.IOException;
} }
@Override @Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) {
return sampleStreamWrapper.readData(group, formatHolder, buffer); return sampleStreamWrapper.readData(group, formatHolder, buffer, requireFormat);
} }
@Override @Override

View File

@ -290,7 +290,8 @@ import java.util.LinkedList;
chunkSource.maybeThrowError(); chunkSource.maybeThrowError();
} }
/* package */ int readData(int group, FormatHolder formatHolder, DecoderInputBuffer buffer) { /* package */ int readData(int group, FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean requireFormat) {
if (isPendingReset()) { if (isPendingReset()) {
return C.RESULT_NOTHING_READ; return C.RESULT_NOTHING_READ;
} }
@ -307,8 +308,8 @@ import java.util.LinkedList;
} }
downstreamTrackFormat = trackFormat; downstreamTrackFormat = trackFormat;
return sampleQueues.valueAt(group).readData(formatHolder, buffer, loadingFinished, return sampleQueues.valueAt(group).readData(formatHolder, buffer, requireFormat,
lastSeekPositionUs); loadingFinished, lastSeekPositionUs);
} }
/* package */ void skipToKeyframeBefore(int group, long timeUs) { /* package */ void skipToKeyframeBefore(int group, long timeUs) {

View File

@ -189,7 +189,7 @@ public final class TextRenderer extends BaseRenderer implements Callback {
} }
} }
// Try and read the next subtitle from the source. // Try and read the next subtitle from the source.
int result = readSource(formatHolder, nextInputBuffer); int result = readSource(formatHolder, nextInputBuffer, false);
if (result == C.RESULT_BUFFER_READ) { if (result == C.RESULT_BUFFER_READ) {
if (nextInputBuffer.isEndOfStream()) { if (nextInputBuffer.isEndOfStream()) {
inputStreamEnded = true; inputStreamEnded = true;