diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java index b19f398d86e..6101c79b7f0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaPeriod.java @@ -27,7 +27,6 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader.Loadable; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; @@ -52,16 +51,20 @@ private final int eventSourceId; private final TrackGroupArray tracks; private final ArrayList sampleStreams; + // Package private to avoid thunk methods. /* package */ final Loader loader; /* package */ final Format format; + /* package */ final boolean treatLoadErrorsAsEndOfStream; /* package */ boolean loadingFinished; + /* package */ boolean loadingSucceeded; /* package */ byte[] sampleData; /* package */ int sampleSize; + private int errorCount; public SingleSampleMediaPeriod(Uri uri, DataSource.Factory dataSourceFactory, Format format, int minLoadableRetryCount, Handler eventHandler, EventListener eventListener, - int eventSourceId) { + int eventSourceId, boolean treatLoadErrorsAsEndOfStream) { this.uri = uri; this.dataSourceFactory = dataSourceFactory; this.format = format; @@ -69,6 +72,7 @@ public SingleSampleMediaPeriod(Uri uri, DataSource.Factory dataSourceFactory, Fo this.eventHandler = eventHandler; this.eventListener = eventListener; this.eventSourceId = eventSourceId; + this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream; tracks = new TrackGroupArray(new TrackGroup(format)); sampleStreams = new ArrayList<>(); loader = new Loader("Loader:SingleSampleMediaPeriod"); @@ -85,7 +89,7 @@ public void prepare(Callback callback, long positionUs) { @Override public void maybeThrowPrepareError() throws IOException { - loader.maybeThrowError(); + // Do nothing. } @Override @@ -157,6 +161,7 @@ public void onLoadCompleted(SourceLoadable loadable, long elapsedRealtimeMs, sampleSize = loadable.sampleSize; sampleData = loadable.sampleData; loadingFinished = true; + loadingSucceeded = true; } @Override @@ -169,6 +174,11 @@ public void onLoadCanceled(SourceLoadable loadable, long elapsedRealtimeMs, long public int onLoadError(SourceLoadable loadable, long elapsedRealtimeMs, long loadDurationMs, IOException error) { notifyLoadError(error); + errorCount++; + if (treatLoadErrorsAsEndOfStream && errorCount >= minLoadableRetryCount) { + loadingFinished = true; + return Loader.DONT_RETRY; + } return Loader.RETRY; } @@ -206,7 +216,9 @@ public boolean isReady() { @Override public void maybeThrowError() throws IOException { - loader.maybeThrowError(); + if (!treatLoadErrorsAsEndOfStream) { + loader.maybeThrowError(); + } } @Override @@ -219,19 +231,19 @@ public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, formatHolder.format = format; streamState = STREAM_STATE_SEND_SAMPLE; return C.RESULT_FORMAT_READ; - } - - Assertions.checkState(streamState == STREAM_STATE_SEND_SAMPLE); - if (!loadingFinished) { - return C.RESULT_NOTHING_READ; - } else { - buffer.timeUs = 0; - buffer.addFlag(C.BUFFER_FLAG_KEY_FRAME); - buffer.ensureSpaceForWrite(sampleSize); - buffer.data.put(sampleData, 0, sampleSize); + } else if (loadingFinished) { + if (loadingSucceeded) { + buffer.timeUs = 0; + buffer.addFlag(C.BUFFER_FLAG_KEY_FRAME); + buffer.ensureSpaceForWrite(sampleSize); + buffer.data.put(sampleData, 0, sampleSize); + } else { + buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); + } streamState = STREAM_STATE_END_OF_STREAM; return C.RESULT_BUFFER_READ; } + return C.RESULT_NOTHING_READ; } @Override diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index 5b190078fd1..dd901958fd7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -57,21 +57,51 @@ public interface EventListener { private final Handler eventHandler; private final EventListener eventListener; private final int eventSourceId; + private final boolean treatLoadErrorsAsEndOfStream; private final Timeline timeline; + /** + * @param uri The {@link Uri} of the media stream. + * @param dataSourceFactory The factory from which the {@link DataSource} to read the media will + * be obtained. + * @param format The {@link Format} associated with the output track. + * @param durationUs The duration of the media stream in microseconds. + */ public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Format format, long durationUs) { this(uri, dataSourceFactory, format, durationUs, DEFAULT_MIN_LOADABLE_RETRY_COUNT); } + /** + * @param uri The {@link Uri} of the media stream. + * @param dataSourceFactory The factory from which the {@link DataSource} to read the media will + * be obtained. + * @param format The {@link Format} associated with the output track. + * @param durationUs The duration of the media stream in microseconds. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + */ public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Format format, long durationUs, int minLoadableRetryCount) { - this(uri, dataSourceFactory, format, durationUs, minLoadableRetryCount, null, null, 0); + this(uri, dataSourceFactory, format, durationUs, minLoadableRetryCount, null, null, 0, false); } + /** + * @param uri The {@link Uri} of the media stream. + * @param dataSourceFactory The factory from which the {@link DataSource} to read the media will + * be obtained. + * @param format The {@link Format} associated with the output track. + * @param durationUs The duration of the media stream in microseconds. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param eventSourceId An identifier that gets passed to {@code eventListener} methods. + * @param treatLoadErrorsAsEndOfStream If true, load errors will not be propagated by sample + * streams, treating them as ended instead. If false, load errors will be propagated normally + * by {@link SampleStream#maybeThrowError()}. + */ public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Format format, long durationUs, int minLoadableRetryCount, Handler eventHandler, EventListener eventListener, - int eventSourceId) { + int eventSourceId, boolean treatLoadErrorsAsEndOfStream) { this.uri = uri; this.dataSourceFactory = dataSourceFactory; this.format = format; @@ -79,6 +109,7 @@ public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Fo this.eventHandler = eventHandler; this.eventListener = eventListener; this.eventSourceId = eventSourceId; + this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream; timeline = new SinglePeriodTimeline(durationUs, true); } @@ -98,7 +129,7 @@ public void maybeThrowSourceInfoRefreshError() throws IOException { public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { Assertions.checkArgument(id.periodIndex == 0); return new SingleSampleMediaPeriod(uri, dataSourceFactory, format, minLoadableRetryCount, - eventHandler, eventListener, eventSourceId); + eventHandler, eventListener, eventSourceId, treatLoadErrorsAsEndOfStream); } @Override