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 @Override
protected int doPrepare(long positionUs) throws ExoPlaybackException { protected int doPrepare(long positionUs) {
try { boolean sourcePrepared = source.prepare(positionUs);
boolean sourcePrepared = source.prepare(positionUs); if (!sourcePrepared) {
if (!sourcePrepared) { return TrackRenderer.STATE_UNPREPARED;
return TrackRenderer.STATE_UNPREPARED;
}
} catch (IOException e) {
throw new ExoPlaybackException(e);
} }
int trackCount = source.getTrackCount();
for (int i = 0; i < source.getTrackCount(); i++) { for (int i = 0; i < trackCount; i++) {
if (source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.AUDIO_OPUS) if (source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.AUDIO_OPUS)
|| source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.AUDIO_WEBM)) { || source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.AUDIO_WEBM)) {
trackIndex = i; trackIndex = i;
return TrackRenderer.STATE_PREPARED; return TrackRenderer.STATE_PREPARED;
} }
} }
return TrackRenderer.STATE_IGNORE; return TrackRenderer.STATE_IGNORE;
} }
@ -152,42 +147,49 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
if (outputStreamEnded) { if (outputStreamEnded) {
return; return;
} }
try { sourceIsReady = source.continueBuffering(trackIndex, positionUs);
sourceIsReady = source.continueBuffering(trackIndex, positionUs); checkForDiscontinuity();
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();
// Queue input buffers. // Try and read a format if we don't have one already.
while (feedInputBuffer()) {} 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) { } catch (AudioTrack.InitializationException e) {
notifyAudioTrackInitializationError(e); notifyAudioTrackInitializationError(e);
throw new ExoPlaybackException(e); throw new ExoPlaybackException(e);
@ -197,8 +199,6 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
} catch (OpusDecoderException e) { } catch (OpusDecoderException e) {
notifyDecoderError(e); notifyDecoderError(e);
throw new ExoPlaybackException(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) { if (inputStreamEnded) {
return false; return false;
} }
@ -291,7 +291,7 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
return true; return true;
} }
private void checkForDiscontinuity() throws IOException { private void checkForDiscontinuity() {
if (decoder == null) { if (decoder == null) {
return; return;
} }
@ -394,12 +394,23 @@ public class LibopusAudioTrackRenderer extends TrackRenderer implements MediaClo
} }
} }
private void readFormat() throws IOException { @Override
int result = source.readData(trackIndex, currentPositionUs, formatHolder, null, false); 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) { if (result == SampleSource.FORMAT_READ) {
format = formatHolder.format; format = formatHolder.format;
audioTrack.reconfigure(format.getFrameworkMediaFormatV16()); audioTrack.reconfigure(format.getFrameworkMediaFormatV16());
return true;
} }
return false;
} }
@Override @Override

View file

@ -153,23 +153,18 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
@Override @Override
protected int doPrepare(long positionUs) throws ExoPlaybackException { protected int doPrepare(long positionUs) throws ExoPlaybackException {
try { boolean sourcePrepared = source.prepare(positionUs);
boolean sourcePrepared = source.prepare(positionUs); if (!sourcePrepared) {
if (!sourcePrepared) { return TrackRenderer.STATE_UNPREPARED;
return TrackRenderer.STATE_UNPREPARED;
}
} catch (IOException e) {
throw new ExoPlaybackException(e);
} }
int trackCount = source.getTrackCount();
for (int i = 0; i < source.getTrackCount(); i++) { for (int i = 0; i < trackCount; i++) {
if (source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.VIDEO_VP9) if (source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.VIDEO_VP9)
|| source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.VIDEO_WEBM)) { || source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.VIDEO_WEBM)) {
trackIndex = i; trackIndex = i;
return TrackRenderer.STATE_PREPARED; return TrackRenderer.STATE_PREPARED;
} }
} }
return TrackRenderer.STATE_IGNORE; return TrackRenderer.STATE_IGNORE;
} }
@ -178,28 +173,29 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
if (outputStreamEnded) { if (outputStreamEnded) {
return; return;
} }
try { sourceIsReady = source.continueBuffering(trackIndex, positionUs);
sourceIsReady = source.continueBuffering(trackIndex, positionUs); checkForDiscontinuity(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);
// Queue input buffers. // Try and read a format if we don't have one already.
while (feedInputBuffer(positionUs)) {} 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) { } catch (VpxDecoderException e) {
notifyDecoderError(e); notifyDecoderError(e);
throw new ExoPlaybackException(e); throw new ExoPlaybackException(e);
} catch (IOException e) {
throw new ExoPlaybackException(e);
} }
} }
@ -294,7 +290,7 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
surface.unlockCanvasAndPost(canvas); surface.unlockCanvasAndPost(canvas);
} }
private boolean feedInputBuffer(long positionUs) throws IOException, VpxDecoderException { private boolean feedInputBuffer(long positionUs) throws VpxDecoderException {
if (inputStreamEnded) { if (inputStreamEnded) {
return false; return false;
} }
@ -334,7 +330,7 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
return true; return true;
} }
private void checkForDiscontinuity(long positionUs) throws IOException { private void checkForDiscontinuity(long positionUs) {
if (decoder == null) { if (decoder == null) {
return; return;
} }
@ -417,11 +413,22 @@ public class LibvpxVideoTrackRenderer extends TrackRenderer {
source.disable(trackIndex); 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); int result = source.readData(trackIndex, positionUs, formatHolder, null, false);
if (result == SampleSource.FORMAT_READ) { if (result == SampleSource.FORMAT_READ) {
format = formatHolder.format; format = formatHolder.format;
return true;
} }
return false;
} }
@Override @Override

View file

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

View file

@ -266,10 +266,12 @@ import java.util.List;
private void incrementalPrepareInternal() throws ExoPlaybackException { private void incrementalPrepareInternal() throws ExoPlaybackException {
long operationStartTimeMs = SystemClock.elapsedRealtime(); long operationStartTimeMs = SystemClock.elapsedRealtime();
boolean prepared = true; boolean prepared = true;
for (int i = 0; i < renderers.length; i++) { for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) {
if (renderers[i].getState() == TrackRenderer.STATE_UNPREPARED) { TrackRenderer renderer = renderers[rendererIndex];
int state = renderers[i].prepare(positionUs); if (renderer.getState() == TrackRenderer.STATE_UNPREPARED) {
int state = renderer.prepare(positionUs);
if (state == TrackRenderer.STATE_UNPREPARED) { if (state == TrackRenderer.STATE_UNPREPARED) {
renderer.maybeThrowError();
prepared = false; prepared = false;
} }
} }
@ -414,7 +416,14 @@ import java.util.List;
// invocation of this method. // invocation of this method.
renderer.doSomeWork(positionUs, elapsedRealtimeUs); renderer.doSomeWork(positionUs, elapsedRealtimeUs);
allRenderersEnded = allRenderersEnded && renderer.isEnded(); 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) { if (bufferedPositionUs == TrackRenderer.UNKNOWN_TIME_US) {
// We've already encountered a track for which the buffered position is unknown. Hence the // 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 fileDescriptorOffset;
private final long fileDescriptorLength; private final long fileDescriptorLength;
private IOException preparationError;
private MediaExtractor extractor; private MediaExtractor extractor;
private TrackInfo[] trackInfos; private TrackInfo[] trackInfos;
private boolean prepared; private boolean prepared;
@ -128,13 +129,22 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
} }
@Override @Override
public boolean prepare(long positionUs) throws IOException { public boolean prepare(long positionUs) {
if (!prepared) { if (!prepared) {
if (preparationError != null) {
return false;
}
extractor = new MediaExtractor(); extractor = new MediaExtractor();
if (context != null) { try {
extractor.setDataSource(context, uri, headers); if (context != null) {
} else { extractor.setDataSource(context, uri, headers);
extractor.setDataSource(fileDescriptor, fileDescriptorOffset, fileDescriptorLength); } else {
extractor.setDataSource(fileDescriptor, fileDescriptorOffset, fileDescriptorLength);
}
} catch (IOException e) {
preparationError = e;
return false;
} }
trackStates = new int[extractor.getTrackCount()]; trackStates = new int[extractor.getTrackCount()];
@ -232,6 +242,13 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
trackStates[track] = TRACK_STATE_DISABLED; trackStates[track] = TRACK_STATE_DISABLED;
} }
@Override
public void maybeThrowError() throws IOException {
if (preparationError != null) {
throw preparationError;
}
}
@Override @Override
public void seekToUs(long positionUs) { public void seekToUs(long positionUs) {
Assertions.checkState(prepared); Assertions.checkState(prepared);

View file

@ -245,17 +245,13 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
} }
@Override @Override
protected int doPrepare(long positionUs) throws ExoPlaybackException { protected int doPrepare(long positionUs) {
try { boolean sourcePrepared = source.prepare(positionUs);
boolean sourcePrepared = source.prepare(positionUs); if (!sourcePrepared) {
if (!sourcePrepared) { return TrackRenderer.STATE_UNPREPARED;
return TrackRenderer.STATE_UNPREPARED;
}
} catch (IOException e) {
throw new ExoPlaybackException(e);
} }
int trackCount = source.getTrackCount();
for (int i = 0; i < source.getTrackCount(); i++) { for (int i = 0; i < trackCount; i++) {
// TODO: Right now this is getting the mime types of the container format // 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 // (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). // 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_PREPARED;
} }
} }
return TrackRenderer.STATE_IGNORE; return TrackRenderer.STATE_IGNORE;
} }
@ -489,39 +484,35 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer {
@Override @Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
try { sourceState = source.continueBuffering(trackIndex, positionUs)
sourceState = source.continueBuffering(trackIndex, positionUs) ? (sourceState == SOURCE_STATE_NOT_READY ? SOURCE_STATE_READY : sourceState)
? (sourceState == SOURCE_STATE_NOT_READY ? SOURCE_STATE_READY : sourceState) : SOURCE_STATE_NOT_READY;
: SOURCE_STATE_NOT_READY; checkForDiscontinuity(positionUs);
checkForDiscontinuity(positionUs); if (format == null) {
if (format == null) { readFormat(positionUs);
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);
} }
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); int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.FORMAT_READ) { if (result == SampleSource.FORMAT_READ) {
onInputFormatChanged(formatHolder); onInputFormatChanged(formatHolder);
} }
} }
private void checkForDiscontinuity(long positionUs) throws IOException, ExoPlaybackException { private void checkForDiscontinuity(long positionUs) throws ExoPlaybackException {
if (codec == null) { if (codec == null) {
return; 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 * @param firstFeed True if this is the first call to this method from the current invocation of
* {@link #doSomeWork(long, long)}. False otherwise. * {@link #doSomeWork(long, long)}. False otherwise.
* @return True if it may be possible to feed more input data. 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. * @throws ExoPlaybackException If an error occurs feeding the input buffer.
*/ */
private boolean feedInputBuffer(long positionUs, boolean firstFeed) private boolean feedInputBuffer(long positionUs, boolean firstFeed) throws ExoPlaybackException {
throws IOException, ExoPlaybackException {
if (inputStreamEnded if (inputStreamEnded
|| codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) { || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
// The input stream has ended, or we need to re-initialize the codec but are still waiting // 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; return false;
} }
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@Override @Override
protected boolean isEnded() { protected boolean isEnded() {
return outputStreamEnded; return outputStreamEnded;

View file

@ -76,9 +76,8 @@ public interface SampleSource {
* *
* @param positionUs The player's current playback position. * @param positionUs The player's current playback position.
* @return True if the source was prepared successfully, false otherwise. * @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. * Returns the number of tracks exposed by the source.
@ -116,6 +115,14 @@ public interface SampleSource {
*/ */
public void disable(int track); 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. * 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. * @param positionUs The current playback position.
* @return True if the track has available samples, or if the end of the stream has been * @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. * 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. * 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. * {@link #DISCONTINUITY_READ} or {@link #NOTHING_READ} can be returned.
* @return The result, which can be {@link #SAMPLE_READ}, {@link #FORMAT_READ}, * @return The result, which can be {@link #SAMPLE_READ}, {@link #FORMAT_READ},
* {@link #DISCONTINUITY_READ}, {@link #NOTHING_READ} or {@link #END_OF_STREAM}. * {@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, 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. * 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. * @param positionUs The player's current playback position.
* @return The current state (one of the STATE_* constants), for convenience. * @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 { /* package */ final int prepare(long positionUs) throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_UNPREPARED); 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 * @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 * then {@link #start} must be called immediately after this method returns (unless a
* {@link ExoPlaybackException} is thrown). * {@link ExoPlaybackException} is thrown).
* @throws ExoPlaybackException If an error occurs.
*/ */
/* package */ final void enable(long positionUs, boolean joining) throws ExoPlaybackException { /* package */ final void enable(long positionUs, boolean joining) throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_PREPARED); 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 * Starts the renderer, meaning that calls to {@link #doSomeWork(long, long)} will cause the
* track to be rendered. * track to be rendered.
*
* @throws ExoPlaybackException If an error occurs.
*/ */
/* package */ final void start() throws ExoPlaybackException { /* package */ final void start() throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_ENABLED); Assertions.checkState(state == TrackRenderer.STATE_ENABLED);
@ -184,6 +188,8 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
/** /**
* Stops the renderer. * Stops the renderer.
*
* @throws ExoPlaybackException If an error occurs.
*/ */
/* package */ final void stop() throws ExoPlaybackException { /* package */ final void stop() throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_STARTED); Assertions.checkState(state == TrackRenderer.STATE_STARTED);
@ -204,6 +210,8 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
/** /**
* Disable the renderer. * Disable the renderer.
*
* @throws ExoPlaybackException If an error occurs.
*/ */
/* package */ final void disable() throws ExoPlaybackException { /* package */ final void disable() throws ExoPlaybackException {
Assertions.checkState(state == TrackRenderer.STATE_ENABLED); Assertions.checkState(state == TrackRenderer.STATE_ENABLED);
@ -224,6 +232,8 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
/** /**
* Releases the renderer. * Releases the renderer.
*
* @throws ExoPlaybackException If an error occurs.
*/ */
/* package */ final void release() throws ExoPlaybackException { /* package */ final void release() throws ExoPlaybackException {
Assertions.checkState(state != TrackRenderer.STATE_ENABLED Assertions.checkState(state != TrackRenderer.STATE_ENABLED
@ -297,6 +307,15 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
protected abstract void doSomeWork(long positionUs, long elapsedRealtimeUs) protected abstract void doSomeWork(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException; 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. * Returns the duration of the media being rendered.
* <p> * <p>

View file

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

View file

@ -94,13 +94,12 @@ public interface ChunkSource {
long playbackPositionUs, ChunkOperationHolder out); long playbackPositionUs, ChunkOperationHolder out);
/** /**
* If the {@link ChunkSource} is currently unable to provide chunks through * If the source is currently having difficulty providing chunks, then this method throws the
* {@link ChunkSource#getChunkOperation}, then this method returns the underlying cause. Returns * underlying error. Otherwise does nothing.
* null otherwise.
* *
* @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 * 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 @Override
public IOException getError() { public void maybeThrowError() throws IOException {
return null; selectedSource.maybeThrowError();
} }
@Override @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.DataSource;
import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.DataSpec;
import java.io.IOException;
import java.util.List; import java.util.List;
/** /**
@ -94,8 +93,8 @@ public class SingleSampleChunkSource implements ChunkSource {
} }
@Override @Override
public IOException getError() { public void maybeThrowError() {
return null; // Do nothing.
} }
@Override @Override

View file

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

View file

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

View file

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

View file

@ -90,16 +90,13 @@ public class MetadataTrackRenderer<T> extends TrackRenderer implements Callback
} }
@Override @Override
protected int doPrepare(long positionUs) throws ExoPlaybackException { protected int doPrepare(long positionUs) {
try { boolean sourcePrepared = source.prepare(positionUs);
boolean sourcePrepared = source.prepare(positionUs); if (!sourcePrepared) {
if (!sourcePrepared) { return TrackRenderer.STATE_UNPREPARED;
return TrackRenderer.STATE_UNPREPARED;
}
} catch (IOException e) {
throw new ExoPlaybackException(e);
} }
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)) { if (metadataParser.canParse(source.getTrackInfo(i).mimeType)) {
trackIndex = i; trackIndex = i;
return TrackRenderer.STATE_PREPARED; return TrackRenderer.STATE_PREPARED;
@ -128,29 +125,21 @@ public class MetadataTrackRenderer<T> extends TrackRenderer implements Callback
@Override @Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) protected void doSomeWork(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException { throws ExoPlaybackException {
try { source.continueBuffering(trackIndex, positionUs);
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);
}
if (!inputStreamEnded && pendingMetadata == null) { if (!inputStreamEnded && pendingMetadata == null) {
try {
int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false); int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
if (result == SampleSource.SAMPLE_READ) { if (result == SampleSource.SAMPLE_READ) {
pendingMetadataTimestamp = sampleHolder.timeUs; 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(); sampleHolder.data.clear();
} else if (result == SampleSource.END_OF_STREAM) { } else if (result == SampleSource.END_OF_STREAM) {
inputStreamEnded = true; 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) { 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 @Override
protected void onDisabled() { protected void onDisabled() {
pendingMetadata = null; pendingMetadata = null;

View file

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

View file

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

View file

@ -92,16 +92,13 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
} }
@Override @Override
protected int doPrepare(long positionUs) throws ExoPlaybackException { protected int doPrepare(long positionUs) {
try { boolean sourcePrepared = source.prepare(positionUs);
boolean sourcePrepared = source.prepare(positionUs); if (!sourcePrepared) {
if (!sourcePrepared) { return TrackRenderer.STATE_UNPREPARED;
return TrackRenderer.STATE_UNPREPARED;
}
} catch (IOException e) {
throw new ExoPlaybackException(e);
} }
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)) { if (eia608Parser.canParse(source.getTrackInfo(i).mimeType)) {
trackIndex = i; trackIndex = i;
return TrackRenderer.STATE_PREPARED; return TrackRenderer.STATE_PREPARED;
@ -133,13 +130,7 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
@Override @Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
try { source.continueBuffering(trackIndex, positionUs);
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);
}
if (isSamplePending()) { if (isSamplePending()) {
maybeParsePendingSample(positionUs); maybeParsePendingSample(positionUs);
@ -147,17 +138,11 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
int result = inputStreamEnded ? SampleSource.END_OF_STREAM : SampleSource.SAMPLE_READ; int result = inputStreamEnded ? SampleSource.END_OF_STREAM : SampleSource.SAMPLE_READ;
while (!isSamplePending() && result == SampleSource.SAMPLE_READ) { while (!isSamplePending() && result == SampleSource.SAMPLE_READ) {
try { result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false); if (result == SampleSource.SAMPLE_READ) {
if (result == SampleSource.SAMPLE_READ) { maybeParsePendingSample(positionUs);
maybeParsePendingSample(positionUs); } else if (result == SampleSource.END_OF_STREAM) {
} else if (result == SampleSource.END_OF_STREAM) { inputStreamEnded = true;
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);
} }
} }
@ -181,6 +166,15 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
source.disable(trackIndex); source.disable(trackIndex);
} }
@Override
protected void maybeThrowError() throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@Override @Override
protected long getDurationUs() { protected long getDurationUs() {
return source.getTrackInfo(trackIndex).durationUs; 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 * Throws the error that affected the most recent attempt to load the manifest. Does nothing if
* most recent attempt was successful. * 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() { public void maybeThrowError() throws IOException {
if (loadExceptionCount <= 1) { // Don't throw an exception until at least 1 retry attempt has been made.
// Don't report an exception until at least 1 retry attempt has been made. if (loadException == null || loadExceptionCount <= 1) {
return null; return;
} }
return loadException; throw loadException;
} }
/** /**