Improve error propagation

This commit is contained in:
Oliver Woodman 2015-07-21 10:07:49 +01:00
parent 5df6854fea
commit a2f10399e7
20 changed files with 345 additions and 299 deletions

View file

@ -126,24 +126,19 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
}
@Override
protected int doPrepare(long positionUs) throws ExoPlaybackException {
try {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
} catch (IOException e) {
throw new ExoPlaybackException(e);
protected int doPrepare(long positionUs) {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
for (int i = 0; i < source.getTrackCount(); i++) {
int trackCount = source.getTrackCount();
for (int i = 0; i < trackCount; i++) {
if (source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.AUDIO_OPUS)
|| source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.AUDIO_WEBM)) {
trackIndex = i;
return TrackRenderer.STATE_PREPARED;
}
}
return TrackRenderer.STATE_IGNORE;
}
@ -152,42 +147,49 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
if (outputStreamEnded) {
return;
}
try {
sourceIsReady = source.continueBuffering(trackIndex, positionUs);
checkForDiscontinuity();
if (format == null) {
readFormat();
} else {
// Create the decoder.
if (decoder == null) {
// For opus, the format can contain upto 3 entries in initializationData in the following
// exact order:
// 1) Opus Header Information (required)
// 2) Codec Delay in nanoseconds (required if Seek Preroll is present)
// 3) Seek Preroll in nanoseconds (required if Codec Delay is present)
List<byte[]> initializationData = format.initializationData;
if (initializationData.size() < 1) {
throw new ExoPlaybackException("Missing initialization data");
}
long codecDelayNs = -1;
long seekPreRollNs = -1;
if (initializationData.size() == 3) {
if (initializationData.get(1).length != Long.SIZE
|| initializationData.get(2).length != Long.SIZE) {
throw new ExoPlaybackException("Invalid Codec Delay or Seek Preroll");
}
codecDelayNs = ByteBuffer.wrap(initializationData.get(1)).getLong();
seekPreRollNs = ByteBuffer.wrap(initializationData.get(2)).getLong();
}
decoder =
new OpusDecoderWrapper(initializationData.get(0), codecDelayNs, seekPreRollNs);
decoder.start();
}
renderBuffer();
sourceIsReady = source.continueBuffering(trackIndex, positionUs);
checkForDiscontinuity();
// Queue input buffers.
while (feedInputBuffer()) {}
// Try and read a format if we don't have one already.
if (format == null && !readFormat(positionUs)) {
// We can't make progress without one.
return;
}
// If we don't have a decoder yet, we need to instantiate one.
if (decoder == null) {
// For opus, the format can contain upto 3 entries in initializationData in the following
// exact order:
// 1) Opus Header Information (required)
// 2) Codec Delay in nanoseconds (required if Seek Preroll is present)
// 3) Seek Preroll in nanoseconds (required if Codec Delay is present)
List<byte[]> initializationData = format.initializationData;
if (initializationData.size() < 1) {
throw new ExoPlaybackException("Missing initialization data");
}
long codecDelayNs = -1;
long seekPreRollNs = -1;
if (initializationData.size() == 3) {
if (initializationData.get(1).length != Long.SIZE
|| initializationData.get(2).length != Long.SIZE) {
throw new ExoPlaybackException("Invalid Codec Delay or Seek Preroll");
}
codecDelayNs = ByteBuffer.wrap(initializationData.get(1)).getLong();
seekPreRollNs = ByteBuffer.wrap(initializationData.get(2)).getLong();
}
try {
decoder = new OpusDecoderWrapper(initializationData.get(0), codecDelayNs, seekPreRollNs);
} catch (OpusDecoderException e) {
notifyDecoderError(e);
throw new ExoPlaybackException(e);
}
decoder.start();
}
// Rendering loop.
try {
renderBuffer();
while (feedInputBuffer()) {}
} catch (AudioTrack.InitializationException e) {
notifyAudioTrackInitializationError(e);
throw new ExoPlaybackException(e);
@ -197,8 +199,6 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
} catch (OpusDecoderException e) {
notifyDecoderError(e);
throw new ExoPlaybackException(e);
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@ -249,7 +249,7 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
}
}
private boolean feedInputBuffer() throws IOException, OpusDecoderException {
private boolean feedInputBuffer() throws OpusDecoderException {
if (inputStreamEnded) {
return false;
}
@ -291,7 +291,7 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
return true;
}
private void checkForDiscontinuity() throws IOException {
private void checkForDiscontinuity() {
if (decoder == null) {
return;
}
@ -394,12 +394,23 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
}
}
private void readFormat() throws IOException {
int result = source.readData(trackIndex, currentPositionUs, formatHolder, null, false);
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
private boolean readFormat(long positionUs) {
int result = source.readData(trackIndex, positionUs, formatHolder, null, false);
if (result == SampleSource.FORMAT_READ) {
format = formatHolder.format;
audioTrack.reconfigure(format.getFrameworkMediaFormatV16());
return true;
}
return false;
}
@Override

View file

@ -153,23 +153,18 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
@Override
protected int doPrepare(long positionUs) throws ExoPlaybackException {
try {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
} catch (IOException e) {
throw new ExoPlaybackException(e);
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
for (int i = 0; i < source.getTrackCount(); i++) {
int trackCount = source.getTrackCount();
for (int i = 0; i < trackCount; i++) {
if (source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.VIDEO_VP9)
|| source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.VIDEO_WEBM)) {
trackIndex = i;
return TrackRenderer.STATE_PREPARED;
}
}
return TrackRenderer.STATE_IGNORE;
}
@ -178,28 +173,29 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
if (outputStreamEnded) {
return;
}
try {
sourceIsReady = source.continueBuffering(trackIndex, positionUs);
checkForDiscontinuity(positionUs);
if (format == null) {
readFormat(positionUs);
} else {
// TODO: Add support for dynamic switching between one type of surface to another.
// Create the decoder.
if (decoder == null) {
decoder = new VpxDecoderWrapper(outputRgb);
decoder.start();
}
processOutputBuffer(positionUs, elapsedRealtimeUs);
sourceIsReady = source.continueBuffering(trackIndex, positionUs);
checkForDiscontinuity(positionUs);
// Queue input buffers.
while (feedInputBuffer(positionUs)) {}
}
// Try and read a format if we don't have one already.
if (format == null && !readFormat(positionUs)) {
// We can't make progress without one.
return;
}
// If we don't have a decoder yet, we need to instantiate one.
// TODO: Add support for dynamic switching between one type of surface to another.
if (decoder == null) {
decoder = new VpxDecoderWrapper(outputRgb);
decoder.start();
}
// Rendering loop.
try {
processOutputBuffer(positionUs, elapsedRealtimeUs);
while (feedInputBuffer(positionUs)) {}
} catch (VpxDecoderException e) {
notifyDecoderError(e);
throw new ExoPlaybackException(e);
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@ -294,7 +290,7 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
surface.unlockCanvasAndPost(canvas);
}
private boolean feedInputBuffer(long positionUs) throws IOException, VpxDecoderException {
private boolean feedInputBuffer(long positionUs) throws VpxDecoderException {
if (inputStreamEnded) {
return false;
}
@ -334,7 +330,7 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
return true;
}
private void checkForDiscontinuity(long positionUs) throws IOException {
private void checkForDiscontinuity(long positionUs) {
if (decoder == null) {
return;
}
@ -417,11 +413,22 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
source.disable(trackIndex);
}
private void readFormat(long positionUs) throws IOException {
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
private boolean readFormat(long positionUs) {
int result = source.readData(trackIndex, positionUs, formatHolder, null, false);
if (result == SampleSource.FORMAT_READ) {
format = formatHolder.format;
return true;
}
return false;
}
@Override

View file

@ -25,7 +25,7 @@ package com.google.android.exoplayer;
public class DummyTrackRenderer extends TrackRenderer {
@Override
protected int doPrepare(long positionUs) throws ExoPlaybackException {
protected int doPrepare(long positionUs) {
return STATE_IGNORE;
}
@ -49,6 +49,11 @@ public class DummyTrackRenderer extends TrackRenderer {
throw new IllegalStateException();
}
@Override
protected void maybeThrowError() {
throw new IllegalStateException();
}
@Override
protected long getDurationUs() {
throw new IllegalStateException();

View file

@ -266,10 +266,12 @@ import java.util.List;
private void incrementalPrepareInternal() throws ExoPlaybackException {
long operationStartTimeMs = SystemClock.elapsedRealtime();
boolean prepared = true;
for (int i = 0; i < renderers.length; i++) {
if (renderers[i].getState() == TrackRenderer.STATE_UNPREPARED) {
int state = renderers[i].prepare(positionUs);
for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) {
TrackRenderer renderer = renderers[rendererIndex];
if (renderer.getState() == TrackRenderer.STATE_UNPREPARED) {
int state = renderer.prepare(positionUs);
if (state == TrackRenderer.STATE_UNPREPARED) {
renderer.maybeThrowError();
prepared = false;
}
}
@ -414,7 +416,14 @@ import java.util.List;
// invocation of this method.
renderer.doSomeWork(positionUs, elapsedRealtimeUs);
allRenderersEnded = allRenderersEnded && renderer.isEnded();
allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded(renderer);
// Determine whether the renderer is ready (or ended). If it's not, throw an error that's
// preventing the renderer from making progress, if such an error exists.
boolean rendererReadyOrEnded = rendererReadyOrEnded(renderer);
if (!rendererReadyOrEnded) {
renderer.maybeThrowError();
}
allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded;
if (bufferedPositionUs == TrackRenderer.UNKNOWN_TIME_US) {
// We've already encountered a track for which the buffered position is unknown. Hence the

View file

@ -76,6 +76,7 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
private final long fileDescriptorOffset;
private final long fileDescriptorLength;
private IOException preparationError;
private MediaExtractor extractor;
private TrackInfo[] trackInfos;
private boolean prepared;
@ -128,13 +129,22 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
}
@Override
public boolean prepare(long positionUs) throws IOException {
public boolean prepare(long positionUs) {
if (!prepared) {
if (preparationError != null) {
return false;
}
extractor = new MediaExtractor();
if (context != null) {
extractor.setDataSource(context, uri, headers);
} else {
extractor.setDataSource(fileDescriptor, fileDescriptorOffset, fileDescriptorLength);
try {
if (context != null) {
extractor.setDataSource(context, uri, headers);
} else {
extractor.setDataSource(fileDescriptor, fileDescriptorOffset, fileDescriptorLength);
}
} catch (IOException e) {
preparationError = e;
return false;
}
trackStates = new int[extractor.getTrackCount()];
@ -232,6 +242,13 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
trackStates[track] = TRACK_STATE_DISABLED;
}
@Override
public void maybeThrowError() throws IOException {
if (preparationError != null) {
throw preparationError;
}
}
@Override
public void seekToUs(long positionUs) {
Assertions.checkState(prepared);

View file

@ -245,17 +245,13 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
@Override
protected int doPrepare(long positionUs) throws ExoPlaybackException {
try {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
} catch (IOException e) {
throw new ExoPlaybackException(e);
protected int doPrepare(long positionUs) {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
for (int i = 0; i < source.getTrackCount(); i++) {
int trackCount = source.getTrackCount();
for (int i = 0; i < trackCount; i++) {
// TODO: Right now this is getting the mime types of the container format
// (e.g. audio/mp4 and video/mp4 for fragmented mp4). It needs to be getting the mime types
// of the actual samples (e.g. audio/mp4a-latm and video/avc).
@ -264,7 +260,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
return TrackRenderer.STATE_PREPARED;
}
}
return TrackRenderer.STATE_IGNORE;
}
@ -489,39 +484,35 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
try {
sourceState = source.continueBuffering(trackIndex, positionUs)
? (sourceState == SOURCE_STATE_NOT_READY ? SOURCE_STATE_READY : sourceState)
: SOURCE_STATE_NOT_READY;
checkForDiscontinuity(positionUs);
if (format == null) {
readFormat(positionUs);
}
if (codec == null && shouldInitCodec()) {
maybeInitCodec();
}
if (codec != null) {
TraceUtil.beginSection("drainAndFeed");
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
if (feedInputBuffer(positionUs, true)) {
while (feedInputBuffer(positionUs, false)) {}
}
TraceUtil.endSection();
}
codecCounters.ensureUpdated();
} catch (IOException e) {
throw new ExoPlaybackException(e);
sourceState = source.continueBuffering(trackIndex, positionUs)
? (sourceState == SOURCE_STATE_NOT_READY ? SOURCE_STATE_READY : sourceState)
: SOURCE_STATE_NOT_READY;
checkForDiscontinuity(positionUs);
if (format == null) {
readFormat(positionUs);
}
if (codec == null && shouldInitCodec()) {
maybeInitCodec();
}
if (codec != null) {
TraceUtil.beginSection("drainAndFeed");
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
if (feedInputBuffer(positionUs, true)) {
while (feedInputBuffer(positionUs, false)) {}
}
TraceUtil.endSection();
}
codecCounters.ensureUpdated();
}
private void readFormat(long positionUs) throws IOException, ExoPlaybackException {
private void readFormat(long positionUs) throws ExoPlaybackException {
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.FORMAT_READ) {
onInputFormatChanged(formatHolder);
}
}
private void checkForDiscontinuity(long positionUs) throws IOException, ExoPlaybackException {
private void checkForDiscontinuity(long positionUs) throws ExoPlaybackException {
if (codec == null) {
return;
}
@ -560,11 +551,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
* @param firstFeed True if this is the first call to this method from the current invocation of
* {@link #doSomeWork(long, long)}. False otherwise.
* @return True if it may be possible to feed more input data. False otherwise.
* @throws IOException If an error occurs reading data from the upstream source.
* @throws ExoPlaybackException If an error occurs feeding the input buffer.
*/
private boolean feedInputBuffer(long positionUs, boolean firstFeed)
throws IOException, ExoPlaybackException {
private boolean feedInputBuffer(long positionUs, boolean firstFeed) throws ExoPlaybackException {
if (inputStreamEnded
|| codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
// The input stream has ended, or we need to re-initialize the codec but are still waiting
@ -785,6 +774,15 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
return false;
}
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@Override
protected boolean isEnded() {
return outputStreamEnded;

View file

@ -76,9 +76,8 @@ public interface SampleSource {
*
* @param positionUs The player's current playback position.
* @return True if the source was prepared successfully, false otherwise.
* @throws IOException If an error occurred preparing the source.
*/
public boolean prepare(long positionUs) throws IOException;
public boolean prepare(long positionUs);
/**
* Returns the number of tracks exposed by the source.
@ -116,6 +115,14 @@ public interface SampleSource {
*/
public void disable(int track);
/**
* If the source is currently having difficulty preparing or loading samples, then this method
* throws the underlying error. Otherwise does nothing.
*
* @throws IOException The underlying error.
*/
public void maybeThrowError() throws IOException;
/**
* Indicates to the source that it should still be buffering data for the specified track.
*
@ -123,9 +130,8 @@ public interface SampleSource {
* @param positionUs The current playback position.
* @return True if the track has available samples, or if the end of the stream has been
* reached. False if more data needs to be buffered for samples to become available.
* @throws IOException If an error occurred reading from the source.
*/
public boolean continueBuffering(int track, long positionUs) throws IOException;
public boolean continueBuffering(int track, long positionUs);
/**
* Attempts to read either a sample, a new format or or a discontinuity from the source.
@ -147,10 +153,9 @@ public interface SampleSource {
* {@link #DISCONTINUITY_READ} or {@link #NOTHING_READ} can be returned.
* @return The result, which can be {@link #SAMPLE_READ}, {@link #FORMAT_READ},
* {@link #DISCONTINUITY_READ}, {@link #NOTHING_READ} or {@link #END_OF_STREAM}.
* @throws IOException If an error occurred reading from the source.
*/
public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) throws IOException;
SampleHolder sampleHolder, boolean onlyReadDiscontinuity);
/**
* Seeks to the specified time in microseconds.

View file

@ -107,6 +107,7 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
*
* @param positionUs The player's current playback position.
* @return The current state (one of the STATE_* constants), for convenience.
* @throws ExoPlaybackException If an error occurs.
*/
/* package */ final int prepare(long positionUs) throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_UNPREPARED);
@ -139,6 +140,7 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* @param joining Whether this renderer is being enabled to join an ongoing playback. If true
* then {@link #start} must be called immediately after this method returns (unless a
* {@link ExoPlaybackException} is thrown).
* @throws ExoPlaybackException If an error occurs.
*/
/* package */ final void enable(long positionUs, boolean joining) throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_PREPARED);
@ -164,6 +166,8 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
/**
* Starts the renderer, meaning that calls to {@link #doSomeWork(long, long)} will cause the
* track to be rendered.
*
* @throws ExoPlaybackException If an error occurs.
*/
/* package */ final void start() throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_ENABLED);
@ -184,6 +188,8 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
/**
* Stops the renderer.
*
* @throws ExoPlaybackException If an error occurs.
*/
/* package */ final void stop() throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_STARTED);
@ -204,6 +210,8 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
/**
* Disable the renderer.
*
* @throws ExoPlaybackException If an error occurs.
*/
/* package */ final void disable() throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_ENABLED);
@ -224,6 +232,8 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
/**
* Releases the renderer.
*
* @throws ExoPlaybackException If an error occurs.
*/
/* package */ final void release() throws ExoPlaybackException {
Assertions.checkState(state != TrackRenderer.STATE_ENABLED
@ -297,6 +307,15 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
protected abstract void doSomeWork(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException;
/**
* Throws an error that's preventing the renderer from making progress or buffering more data at
* this point in time.
*
* @throws ExoPlaybackException An error that's preventing the renderer from making progress or
* buffering more data.
*/
protected abstract void maybeThrowError() throws ExoPlaybackException;
/**
* Returns the duration of the media being rendered.
* <p>

View file

@ -188,23 +188,18 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
}
@Override
public boolean continueBuffering(int track, long positionUs) throws IOException {
public boolean continueBuffering(int track, long positionUs) {
Assertions.checkState(state == STATE_ENABLED);
Assertions.checkState(track == 0);
downstreamPositionUs = positionUs;
chunkSource.continueBuffering(positionUs);
updateLoadControl();
boolean haveSamples = !sampleQueue.isEmpty();
if (!haveSamples) {
maybeThrowLoadableException();
}
return loadingFinished || haveSamples;
return loadingFinished || !sampleQueue.isEmpty();
}
@Override
public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) throws IOException {
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) {
Assertions.checkState(state == STATE_ENABLED);
Assertions.checkState(track == 0);
downstreamPositionUs = positionUs;
@ -219,7 +214,6 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
}
if (isPendingReset()) {
maybeThrowLoadableException();
return NOTHING_READ;
}
@ -252,7 +246,6 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
if (loadingFinished) {
return END_OF_STREAM;
}
maybeThrowLoadableException();
return NOTHING_READ;
}
@ -263,7 +256,6 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
return SAMPLE_READ;
}
maybeThrowLoadableException();
return NOTHING_READ;
}
@ -295,15 +287,12 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
pendingDiscontinuity = true;
}
private void maybeThrowLoadableException() throws IOException {
@Override
public void maybeThrowError() throws IOException {
if (currentLoadableException != null && currentLoadableExceptionCount > minLoadableRetryCount) {
throw currentLoadableException;
}
if (sampleQueue.isEmpty() && currentLoadableHolder.chunk == null) {
IOException chunkSourceException = chunkSource.getError();
if (chunkSourceException != null) {
throw chunkSourceException;
}
} else if (currentLoadableHolder.chunk == null) {
chunkSource.maybeThrowError();
}
}

View file

@ -94,13 +94,12 @@ public interface ChunkSource {
long playbackPositionUs, ChunkOperationHolder out);
/**
* If the {@link ChunkSource} is currently unable to provide chunks through
* {@link ChunkSource#getChunkOperation}, then this method returns the underlying cause. Returns
* null otherwise.
* If the source is currently having difficulty providing chunks, then this method throws the
* underlying error. Otherwise does nothing.
*
* @return An {@link IOException}, or null.
* @throws IOException The underlying error.
*/
IOException getError();
void maybeThrowError() throws IOException;
/**
* Invoked when the {@link ChunkSampleSource} has finished loading a chunk obtained from this

View file

@ -89,8 +89,8 @@ public class MultiTrackChunkSource implements ChunkSource, ExoPlayerComponent {
}
@Override
public IOException getError() {
return null;
public void maybeThrowError() throws IOException {
selectedSource.maybeThrowError();
}
@Override

View file

@ -21,7 +21,6 @@ import com.google.android.exoplayer.TrackInfo;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import java.io.IOException;
import java.util.List;
/**
@ -94,8 +93,8 @@ public class SingleSampleChunkSource implements ChunkSource {
}
@Override
public IOException getError() {
return null;
public void maybeThrowError() {
// Do nothing.
}
@Override

View file

@ -514,9 +514,12 @@ public class DashChunkSource implements ChunkSource {
}
@Override
public IOException getError() {
return fatalError != null ? fatalError
: (manifestFetcher != null ? manifestFetcher.getError() : null);
public void maybeThrowError() throws IOException {
if (fatalError != null) {
throw fatalError;
} else if (manifestFetcher != null) {
manifestFetcher.maybeThrowError();
}
}
@Override

View file

@ -172,7 +172,7 @@ public class ExtractorSampleSource implements SampleSource, SampleSourceReader,
}
@Override
public boolean prepare(long positionUs) throws IOException {
public boolean prepare(long positionUs) {
if (prepared) {
return true;
}
@ -198,10 +198,9 @@ public class ExtractorSampleSource implements SampleSource, SampleSourceReader,
}
prepared = true;
return true;
} else {
maybeThrowLoadableException();
return false;
}
return false;
}
@Override
@ -246,7 +245,7 @@ public class ExtractorSampleSource implements SampleSource, SampleSourceReader,
}
@Override
public boolean continueBuffering(int track, long playbackPositionUs) throws IOException {
public boolean continueBuffering(int track, long playbackPositionUs) {
Assertions.checkState(prepared);
Assertions.checkState(trackEnabledStates[track]);
downstreamPositionUs = playbackPositionUs;
@ -258,16 +257,12 @@ public class ExtractorSampleSource implements SampleSource, SampleSourceReader,
if (isPendingReset()) {
return false;
}
if (sampleQueues.valueAt(track).isEmpty()) {
maybeThrowLoadableException();
return false;
}
return true;
return !sampleQueues.valueAt(track).isEmpty();
}
@Override
public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) throws IOException {
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) {
downstreamPositionUs = playbackPositionUs;
if (pendingDiscontinuities[track]) {
@ -276,7 +271,6 @@ public class ExtractorSampleSource implements SampleSource, SampleSourceReader,
}
if (onlyReadDiscontinuity || isPendingReset()) {
maybeThrowLoadableException();
return NOTHING_READ;
}
@ -304,10 +298,27 @@ public class ExtractorSampleSource implements SampleSource, SampleSourceReader,
return END_OF_STREAM;
}
maybeThrowLoadableException();
return NOTHING_READ;
}
@Override
public void maybeThrowError() throws IOException {
if (currentLoadableException == null) {
return;
}
int minLoadableRetryCountForMedia;
if (minLoadableRetryCount != MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA) {
minLoadableRetryCountForMedia = minLoadableRetryCount;
} else {
minLoadableRetryCountForMedia = seekMap != null && !seekMap.isSeekable()
? DEFAULT_MIN_LOADABLE_RETRY_COUNT_LIVE
: DEFAULT_MIN_LOADABLE_RETRY_COUNT_ON_DEMAND;
}
if (currentLoadableExceptionCount > minLoadableRetryCountForMedia) {
throw currentLoadableException;
}
}
@Override
public void seekToUs(long positionUs) {
Assertions.checkState(prepared);
@ -496,23 +507,6 @@ public class ExtractorSampleSource implements SampleSource, SampleSourceReader,
loader.startLoading(loadable, this);
}
private void maybeThrowLoadableException() throws IOException {
if (currentLoadableException == null) {
return;
}
int minLoadableRetryCountForMedia;
if (minLoadableRetryCount != MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA) {
minLoadableRetryCountForMedia = minLoadableRetryCount;
} else {
minLoadableRetryCountForMedia = seekMap != null && !seekMap.isSeekable()
? DEFAULT_MIN_LOADABLE_RETRY_COUNT_LIVE
: DEFAULT_MIN_LOADABLE_RETRY_COUNT_ON_DEMAND;
}
if (currentLoadableExceptionCount > minLoadableRetryCountForMedia) {
throw currentLoadableException;
}
}
private ExtractingLoadable createLoadableFromStart() {
return new ExtractingLoadable(uri, dataSource, extractor, allocator, requestedBufferSize, 0);
}

View file

@ -125,7 +125,7 @@ public class HlsSampleSource implements SampleSource, SampleSourceReader, Loader
}
@Override
public boolean prepare(long positionUs) throws IOException {
public boolean prepare(long positionUs) {
if (prepared) {
return true;
}
@ -162,7 +162,6 @@ public class HlsSampleSource implements SampleSource, SampleSourceReader, Loader
downstreamPositionUs = positionUs;
}
maybeStartLoading();
maybeThrowLoadableException();
return false;
}
@ -218,7 +217,7 @@ public class HlsSampleSource implements SampleSource, SampleSourceReader, Loader
}
@Override
public boolean continueBuffering(int track, long playbackPositionUs) throws IOException {
public boolean continueBuffering(int track, long playbackPositionUs) {
Assertions.checkState(prepared);
Assertions.checkState(trackEnabledStates[track]);
downstreamPositionUs = playbackPositionUs;
@ -232,7 +231,6 @@ public class HlsSampleSource implements SampleSource, SampleSourceReader, Loader
if (isPendingReset() || extractors.isEmpty()) {
return false;
}
for (int extractorIndex = 0; extractorIndex < extractors.size(); extractorIndex++) {
HlsExtractorWrapper extractor = extractors.get(extractorIndex);
if (!extractor.isPrepared()) {
@ -242,13 +240,12 @@ public class HlsSampleSource implements SampleSource, SampleSourceReader, Loader
return true;
}
}
maybeThrowLoadableException();
return false;
}
@Override
public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) throws IOException {
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) {
Assertions.checkState(prepared);
downstreamPositionUs = playbackPositionUs;
@ -262,13 +259,11 @@ public class HlsSampleSource implements SampleSource, SampleSourceReader, Loader
}
if (isPendingReset()) {
maybeThrowLoadableException();
return NOTHING_READ;
}
HlsExtractorWrapper extractor = getCurrentExtractor();
if (!extractor.isPrepared()) {
maybeThrowLoadableException();
return NOTHING_READ;
}
@ -290,7 +285,6 @@ public class HlsSampleSource implements SampleSource, SampleSourceReader, Loader
// next one for the current read.
extractor = extractors.get(++extractorIndex);
if (!extractor.isPrepared()) {
maybeThrowLoadableException();
return NOTHING_READ;
}
}
@ -313,10 +307,16 @@ public class HlsSampleSource implements SampleSource, SampleSourceReader, Loader
return END_OF_STREAM;
}
maybeThrowLoadableException();
return NOTHING_READ;
}
@Override
public void maybeThrowError() throws IOException {
if (currentLoadableException != null && currentLoadableExceptionCount > minLoadableRetryCount) {
throw currentLoadableException;
}
}
@Override
public void seekToUs(long positionUs) {
Assertions.checkState(prepared);
@ -361,6 +361,8 @@ public class HlsSampleSource implements SampleSource, SampleSourceReader, Loader
}
}
// Loader.Callback implementation.
@Override
public void onLoadCompleted(Loadable loadable) {
Assertions.checkState(loadable == currentLoadable);
@ -412,6 +414,8 @@ public class HlsSampleSource implements SampleSource, SampleSourceReader, Loader
maybeStartLoading();
}
// Internal stuff.
/**
* Gets the current extractor from which samples should be read.
* <p>
@ -455,12 +459,6 @@ public class HlsSampleSource implements SampleSource, SampleSourceReader, Loader
return false;
}
private void maybeThrowLoadableException() throws IOException {
if (currentLoadableException != null && currentLoadableExceptionCount > minLoadableRetryCount) {
throw currentLoadableException;
}
}
private void restartFrom(long positionUs) {
pendingResetPositionUs = positionUs;
loadingFinished = false;

View file

@ -90,16 +90,13 @@ public class MetadataTrackRenderer<T> extends TrackRenderer implements Callback
}
@Override
protected int doPrepare(long positionUs) throws ExoPlaybackException {
try {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
} catch (IOException e) {
throw new ExoPlaybackException(e);
protected int doPrepare(long positionUs) {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
for (int i = 0; i < source.getTrackCount(); i++) {
int trackCount = source.getTrackCount();
for (int i = 0; i < trackCount; i++) {
if (metadataParser.canParse(source.getTrackInfo(i).mimeType)) {
trackIndex = i;
return TrackRenderer.STATE_PREPARED;
@ -128,29 +125,21 @@ public class MetadataTrackRenderer<T> extends TrackRenderer implements Callback
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException {
try {
source.continueBuffering(trackIndex, positionUs);
} catch (IOException e) {
// TODO: This should be propagated, but in the current design propagation may occur too
// early. See [Internal b/22291244].
// throw new ExoPlaybackException(e);
}
source.continueBuffering(trackIndex, positionUs);
if (!inputStreamEnded && pendingMetadata == null) {
try {
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) {
pendingMetadataTimestamp = sampleHolder.timeUs;
pendingMetadata = metadataParser.parse(sampleHolder.data.array(), sampleHolder.size);
try {
pendingMetadata = metadataParser.parse(sampleHolder.data.array(), sampleHolder.size);
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
sampleHolder.data.clear();
} else if (result == SampleSource.END_OF_STREAM) {
inputStreamEnded = true;
}
} catch (IOException e) {
// TODO: This should be propagated, but in the current design propagation may occur too
// early. See [Internal b/22291244].
// throw new ExoPlaybackException(e);
}
}
if (pendingMetadata != null && pendingMetadataTimestamp <= positionUs) {
@ -159,6 +148,15 @@ public class MetadataTrackRenderer<T> extends TrackRenderer implements Callback
}
}
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@Override
protected void onDisabled() {
pendingMetadata = null;

View file

@ -324,9 +324,12 @@ public class SmoothStreamingChunkSource implements ChunkSource {
}
@Override
public IOException getError() {
return fatalError != null ? fatalError
: (manifestFetcher != null ? manifestFetcher.getError() : null);
public void maybeThrowError() throws IOException {
if (fatalError != null) {
throw fatalError;
} else {
manifestFetcher.maybeThrowError();
}
}
@Override

View file

@ -151,17 +151,14 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
}
@Override
protected int doPrepare(long positionUs) throws ExoPlaybackException {
try {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
} catch (IOException e) {
throw new ExoPlaybackException(e);
protected int doPrepare(long positionUs) {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
int trackCount = source.getTrackCount();
for (int i = 0; i < subtitleParsers.length; i++) {
for (int j = 0; j < source.getTrackCount(); j++) {
for (int j = 0; j < trackCount; j++) {
if (subtitleParsers[i].canParse(source.getTrackInfo(j).mimeType)) {
parserIndex = i;
trackIndex = j;
@ -197,11 +194,7 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
try {
source.continueBuffering(trackIndex, positionUs);
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
source.continueBuffering(trackIndex, positionUs);
if (nextSubtitle == null) {
try {
@ -240,17 +233,13 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
if (!inputStreamEnded && nextSubtitle == null && !parserHelper.isParsing()) {
// Try and read the next subtitle from the source.
try {
SampleHolder sampleHolder = parserHelper.getSampleHolder();
sampleHolder.clearData();
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) {
parserHelper.startParseOperation();
} else if (result == SampleSource.END_OF_STREAM) {
inputStreamEnded = true;
}
} catch (IOException e) {
throw new ExoPlaybackException(e);
SampleHolder sampleHolder = parserHelper.getSampleHolder();
sampleHolder.clearData();
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) {
parserHelper.startParseOperation();
} else if (result == SampleSource.END_OF_STREAM) {
inputStreamEnded = true;
}
}
}
@ -271,6 +260,15 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
source.release();
}
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@Override
protected long getDurationUs() {
return source.getTrackInfo(trackIndex).durationUs;

View file

@ -92,16 +92,13 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
}
@Override
protected int doPrepare(long positionUs) throws ExoPlaybackException {
try {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
} catch (IOException e) {
throw new ExoPlaybackException(e);
protected int doPrepare(long positionUs) {
boolean sourcePrepared = source.prepare(positionUs);
if (!sourcePrepared) {
return TrackRenderer.STATE_UNPREPARED;
}
for (int i = 0; i < source.getTrackCount(); i++) {
int trackCount = source.getTrackCount();
for (int i = 0; i < trackCount; i++) {
if (eia608Parser.canParse(source.getTrackInfo(i).mimeType)) {
trackIndex = i;
return TrackRenderer.STATE_PREPARED;
@ -133,13 +130,7 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
try {
source.continueBuffering(trackIndex, positionUs);
} catch (IOException e) {
// TODO: This should be propagated, but in the current design propagation may occur too
// early. See [Internal b/22291244].
// throw new ExoPlaybackException(e);
}
source.continueBuffering(trackIndex, positionUs);
if (isSamplePending()) {
maybeParsePendingSample(positionUs);
@ -147,17 +138,11 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
int result = inputStreamEnded ? SampleSource.END_OF_STREAM : SampleSource.SAMPLE_READ;
while (!isSamplePending() && result == SampleSource.SAMPLE_READ) {
try {
result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) {
maybeParsePendingSample(positionUs);
} else if (result == SampleSource.END_OF_STREAM) {
inputStreamEnded = true;
}
} catch (IOException e) {
// TODO: This should be propagated, but in the current design propagation may occur too
// early. See [Internal b/22291244].
// throw new ExoPlaybackException(e);
result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) {
maybeParsePendingSample(positionUs);
} else if (result == SampleSource.END_OF_STREAM) {
inputStreamEnded = true;
}
}
@ -181,6 +166,15 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
source.disable(trackIndex);
}
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@Override
protected long getDurationUs() {
return source.getTrackInfo(trackIndex).durationUs;

View file

@ -186,17 +186,17 @@ public class ManifestFetcher<T> implements Loader.Callback {
}
/**
* Gets the error that affected the most recent attempt to load the manifest, or null if the
* most recent attempt was successful.
* Throws the error that affected the most recent attempt to load the manifest. Does nothing if
* the most recent attempt was successful.
*
* @return The error, or null if the most recent attempt was successful.
* @throws IOException The error that affected the most recent attempt to load the manifest.
*/
public IOException getError() {
if (loadExceptionCount <= 1) {
// Don't report an exception until at least 1 retry attempt has been made.
return null;
public void maybeThrowError() throws IOException {
// Don't throw an exception until at least 1 retry attempt has been made.
if (loadException == null || loadExceptionCount <= 1) {
return;
}
return loadException;
throw loadException;
}
/**