Commit a2f10399 by Oliver Woodman

Improve error propagation

parent 5df6854f
Showing with 344 additions and 298 deletions
......@@ -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();
// 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;
}
// Queue input buffers.
while (feedInputBuffer()) {}
// 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
......
......@@ -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
......
......@@ -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;
}
......@@ -50,6 +50,11 @@ public class DummyTrackRenderer extends TrackRenderer {
}
@Override
protected void maybeThrowError() {
throw new IllegalStateException();
}
@Override
protected long getDurationUs() {
throw new IllegalStateException();
}
......
......@@ -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
......
......@@ -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()];
......@@ -233,6 +243,13 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
}
@Override
public void maybeThrowError() throws IOException {
if (preparationError != null) {
throw preparationError;
}
}
@Override
public void seekToUs(long positionUs) {
Assertions.checkState(prepared);
seekToUsInternal(positionUs, false);
......
......@@ -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();
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)) {}
}
codecCounters.ensureUpdated();
} catch (IOException e) {
throw new ExoPlaybackException(e);
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
......@@ -786,6 +775,15 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
}
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@Override
protected boolean isEnded() {
return outputStreamEnded;
}
......
......@@ -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.
......@@ -117,15 +116,22 @@ 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.
*
* @param track The track to continue buffering.
* @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.
......
......@@ -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
......@@ -298,6 +308,15 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
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>
* This method may be called when the renderer is in the following states:
......
......@@ -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();
}
}
......
......@@ -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
......
......@@ -89,8 +89,8 @@ public class MultiTrackChunkSource implements ChunkSource, ExoPlayerComponent {
}
@Override
public IOException getError() {
return null;
public void maybeThrowError() throws IOException {
selectedSource.maybeThrowError();
}
@Override
......
......@@ -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
......
......@@ -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
......
......@@ -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,11 +298,28 @@ 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);
Assertions.checkState(enabledTrackCount > 0);
......@@ -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);
}
......
......@@ -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,11 +307,17 @@ 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);
Assertions.checkState(enabledTrackCount > 0);
......@@ -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;
......
......@@ -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) {
......@@ -160,6 +149,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;
source.disable(trackIndex);
......
......@@ -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
......
......@@ -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;
}
}
}
......@@ -272,6 +261,15 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
}
@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;
}
......
......@@ -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;
}
}
......@@ -182,6 +167,15 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
}
@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;
}
......
......@@ -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;
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment