Commit a4d17282 by olly Committed by Oliver Woodman

Have loader implement retry logic.

This was made possible by the simplification of how DASH/SS
chunk replacement works. It is also a step towards eliminating
continueBuffering(), since continueBuffering() calls are no
longer relied upon to resume a backed off load.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=118774865
parent d83b89cf
......@@ -23,7 +23,6 @@ import com.google.android.exoplayer.util.Assertions;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
import java.io.IOException;
import java.util.Arrays;
......@@ -78,9 +77,6 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
private long pendingResetPositionUs;
private boolean loadingFinished;
private Loader loader;
private IOException currentLoadableException;
private int currentLoadableExceptionCount;
private long currentLoadableExceptionTimestamp;
private int streamState;
private byte[] sampleData;
......@@ -112,9 +108,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
@Override
public void maybeThrowError() throws IOException {
if (currentLoadableException != null && currentLoadableExceptionCount > minLoadableRetryCount) {
throw currentLoadableException;
}
loader.maybeThrowError();
}
@Override
......@@ -122,7 +116,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
if (prepared) {
return true;
}
loader = new Loader("Loader:" + format.sampleMimeType);
loader = new Loader("Loader:" + format.sampleMimeType, minLoadableRetryCount);
prepared = true;
return true;
}
......@@ -146,6 +140,9 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
// Unselect old tracks.
if (!oldStreams.isEmpty()) {
streamState = STREAM_STATE_END_OF_STREAM;
if (loader.isLoading()) {
loader.cancelLoading();
}
}
// Select new tracks.
TrackStream[] newStreams = new TrackStream[newSelections.size()];
......@@ -153,7 +150,6 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
newStreams[0] = this;
streamState = STREAM_STATE_SEND_FORMAT;
pendingResetPositionUs = NO_RESET;
clearCurrentLoadableException();
maybeStartLoading();
}
return newStreams;
......@@ -161,7 +157,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
@Override
public void continueBuffering(long positionUs) {
maybeStartLoading();
// Do nothing.
}
@Override
......@@ -223,51 +219,22 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
}
}
// Private methods.
private void maybeStartLoading() {
if (loadingFinished || streamState == STREAM_STATE_END_OF_STREAM || loader.isLoading()) {
return;
}
if (currentLoadableException != null) {
long elapsedMillis = SystemClock.elapsedRealtime() - currentLoadableExceptionTimestamp;
if (elapsedMillis < getRetryDelayMillis(currentLoadableExceptionCount)) {
return;
}
currentLoadableException = null;
}
loader.startLoading(this, this);
}
private void clearCurrentLoadableException() {
currentLoadableException = null;
currentLoadableExceptionCount = 0;
}
private long getRetryDelayMillis(long errorCount) {
return Math.min((errorCount - 1) * 1000, 5000);
}
// Loader.Callback implementation.
@Override
public void onLoadCompleted(Loadable loadable) {
loadingFinished = true;
clearCurrentLoadableException();
}
@Override
public void onLoadCanceled(Loadable loadable) {
// Never happens.
maybeStartLoading();
}
@Override
public void onLoadError(Loadable loadable, IOException e) {
currentLoadableException = e;
currentLoadableExceptionCount++;
currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime();
public int onLoadError(Loadable loadable, IOException e) {
notifyLoadError(e);
maybeStartLoading();
return Loader.RETRY;
}
// Loadable implementation.
......@@ -303,6 +270,15 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
}
}
// Private methods.
private void maybeStartLoading() {
if (loadingFinished || streamState == STREAM_STATE_END_OF_STREAM || loader.isLoading()) {
return;
}
loader.startLoading(this, this);
}
private void notifyLoadError(final IOException e) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
......
......@@ -81,9 +81,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
private Loader loader;
private boolean loadingFinished;
private boolean trackEnabled;
private IOException currentLoadableException;
private int currentLoadableExceptionCount;
private long currentLoadableExceptionTimestamp;
private long currentLoadStartTimeMs;
private Format downstreamFormat;
......@@ -154,7 +151,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
durationUs = chunkSource.getDurationUs();
TrackGroup tracks = chunkSource.getTracks();
if (tracks != null) {
loader = new Loader("Loader:" + tracks.getFormat(0).containerMimeType);
loader = new Loader("Loader:" + tracks.getFormat(0).containerMimeType, minLoadableRetryCount);
trackGroups = new TrackGroupArray(tracks);
} else {
trackGroups = new TrackGroupArray();
......@@ -315,9 +312,8 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
@Override
public void maybeThrowError() throws IOException {
if (currentLoadableException != null && currentLoadableExceptionCount > minLoadableRetryCount) {
throw currentLoadableException;
} else if (currentLoadableHolder.chunk == null) {
loader.maybeThrowError();
if (currentLoadableHolder.chunk == null) {
chunkSource.maybeThrowError();
}
}
......@@ -345,6 +341,8 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
}
}
// Loadable.Callback implementation.
@Override
public void onLoadCompleted(Loadable loadable) {
long now = SystemClock.elapsedRealtime();
......@@ -373,13 +371,12 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
} else {
sampleQueue.clear();
mediaChunks.clear();
clearCurrentLoadable();
loadControl.trimAllocator();
}
}
@Override
public void onLoadError(Loadable loadable, IOException e) {
public int onLoadError(Loadable loadable, IOException e) {
Chunk currentLoadable = currentLoadableHolder.chunk;
long bytesLoaded = currentLoadable.bytesLoaded();
boolean isMediaChunk = isMediaChunk(currentLoadable);
......@@ -396,13 +393,12 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
clearCurrentLoadable();
notifyLoadError(e);
notifyLoadCanceled(bytesLoaded);
maybeStartLoading();
return Loader.DONT_RETRY;
} else {
currentLoadableException = e;
currentLoadableExceptionCount++;
currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime();
notifyLoadError(e);
return Loader.RETRY;
}
maybeStartLoading();
}
/**
......@@ -431,24 +427,20 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
private void clearCurrentLoadable() {
currentLoadableHolder.chunk = null;
clearCurrentLoadableException();
}
private void clearCurrentLoadableException() {
currentLoadableException = null;
currentLoadableExceptionCount = 0;
}
private void maybeStartLoading() {
if (loader.isLoading()) {
return;
}
long now = SystemClock.elapsedRealtime();
long nextLoadPositionUs = getNextLoadPositionUs();
boolean isBackedOff = currentLoadableException != null;
boolean loadingOrBackedOff = loader.isLoading() || isBackedOff;
// If we're not loading or backed off, evaluate the operation if (a) we don't have the next
// chunk yet and we're not finished, or (b) if the last evaluation was over 2000ms ago.
if (!loadingOrBackedOff && ((currentLoadableHolder.chunk == null && nextLoadPositionUs != -1)
|| (now - lastPerformedBufferOperation > 2000))) {
// Evaluate the operation if (a) we don't have the next chunk yet and we're not finished, or (b)
// if the last evaluation was over 2000ms ago.
if ((currentLoadableHolder.chunk == null && nextLoadPositionUs != -1)
|| (now - lastPerformedBufferOperation > 2000)) {
// Perform the evaluation.
currentLoadableHolder.endOfStream = false;
currentLoadableHolder.queueSize = readOnlyMediaChunks.size();
......@@ -468,41 +460,35 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
}
}
// Update the control with our current state, and determine whether we're the next loader.
boolean nextLoader = loadControl.update(this, downstreamPositionUs, nextLoadPositionUs,
loadingOrBackedOff);
boolean nextLoader = loadControl.update(this, downstreamPositionUs, nextLoadPositionUs, false);
if (!nextLoader) {
// We're not allowed to start loading yet.
return;
}
if (isBackedOff) {
long elapsedMillis = now - currentLoadableExceptionTimestamp;
if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) {
currentLoadableException = null;
loader.startLoading(currentLoadableHolder.chunk, this);
}
Chunk currentLoadable = currentLoadableHolder.chunk;
if (currentLoadable == null) {
// We're allowed to start loading, but have nothing to load.
return;
}
if (!loader.isLoading() && nextLoader) {
Chunk currentLoadable = currentLoadableHolder.chunk;
if (currentLoadable == null) {
// Nothing to load.
return;
}
currentLoadStartTimeMs = SystemClock.elapsedRealtime();
if (isMediaChunk(currentLoadable)) {
BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable;
mediaChunk.init(sampleQueue);
mediaChunks.add(mediaChunk);
if (isPendingReset()) {
pendingResetPositionUs = NO_RESET_PENDING;
}
notifyLoadStarted(mediaChunk.dataSpec.length, mediaChunk.type, mediaChunk.trigger,
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs);
} else {
notifyLoadStarted(currentLoadable.dataSpec.length, currentLoadable.type,
currentLoadable.trigger, currentLoadable.format, -1, -1);
currentLoadStartTimeMs = SystemClock.elapsedRealtime();
if (isMediaChunk(currentLoadable)) {
BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable;
mediaChunk.init(sampleQueue);
mediaChunks.add(mediaChunk);
if (isPendingReset()) {
pendingResetPositionUs = NO_RESET_PENDING;
}
loader.startLoading(currentLoadable, this);
notifyLoadStarted(mediaChunk.dataSpec.length, mediaChunk.type, mediaChunk.trigger,
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs);
} else {
notifyLoadStarted(currentLoadable.dataSpec.length, currentLoadable.type,
currentLoadable.trigger, currentLoadable.format, -1, -1);
}
loader.startLoading(currentLoadable, this);
// Update the load control again to indicate that we're now loading.
loadControl.update(this, downstreamPositionUs, getNextLoadPositionUs(), true);
}
/**
......@@ -549,10 +535,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
return pendingResetPositionUs != NO_RESET_PENDING;
}
private long getRetryDelayMillis(long errorCount) {
return Math.min((errorCount - 1) * 1000, 5000);
}
protected final long usToMs(long timeUs) {
return timeUs / 1000;
}
......
......@@ -123,7 +123,7 @@ public final class UtcTimingElementResolver implements Loader.Callback {
}
private void resolveHttp(UriLoadable.Parser<Long> parser) {
singleUseLoader = new Loader("utctiming");
singleUseLoader = new Loader("utctiming", 0);
singleUseLoadable = new UriLoadable<>(Uri.parse(timingElement.value), dataSource, parser);
singleUseLoader.startLoading(singleUseLoadable, this);
}
......@@ -141,9 +141,10 @@ public final class UtcTimingElementResolver implements Loader.Callback {
}
@Override
public void onLoadError(Loadable loadable, IOException exception) {
public int onLoadError(Loadable loadable, IOException exception) {
releaseLoader();
callback.onTimestampError(timingElement, exception);
return Loader.DONT_RETRY;
}
private void releaseLoader() {
......
......@@ -100,9 +100,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
private TsChunk previousTsLoadable;
private Loader loader;
private IOException currentLoadableException;
private int currentLoadableExceptionCount;
private long currentLoadableExceptionTimestamp;
private long currentLoadStartTimeMs;
public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl,
......@@ -163,7 +160,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
}
// We're not prepared and we haven't loaded what we need.
if (loader == null) {
loader = new Loader("Loader:HLS");
loader = new Loader("Loader:HLS", minLoadableRetryCount);
loadControl.register(this, bufferSizeContribution);
loadControlRegistered = true;
}
......@@ -333,9 +330,8 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
}
/* package */ void maybeThrowError() throws IOException {
if (currentLoadableException != null && currentLoadableExceptionCount > minLoadableRetryCount) {
throw currentLoadableException;
} else if (currentLoadable == null) {
loader.maybeThrowError();
if (currentLoadable == null) {
chunkSource.maybeThrowError();
}
}
......@@ -416,7 +412,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
}
@Override
public void onLoadError(Loadable loadable, IOException e) {
public int onLoadError(Loadable loadable, IOException e) {
long bytesLoaded = currentLoadable.bytesLoaded();
boolean cancelable = !isTsChunk(currentLoadable) || bytesLoaded == 0;
if (chunkSource.onChunkLoadError(currentLoadable, cancelable, e)) {
......@@ -426,13 +422,12 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
clearCurrentLoadable();
notifyLoadError(e);
notifyLoadCanceled(bytesLoaded);
maybeStartLoading();
return Loader.DONT_RETRY;
} else {
currentLoadableException = e;
currentLoadableExceptionCount++;
currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime();
notifyLoadError(e);
return Loader.RETRY;
}
maybeStartLoading();
}
// Internal stuff.
......@@ -642,30 +637,16 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
private void clearCurrentLoadable() {
currentTsLoadable = null;
currentLoadable = null;
currentLoadableException = null;
currentLoadableExceptionCount = 0;
}
private void maybeStartLoading() {
long now = SystemClock.elapsedRealtime();
long nextLoadPositionUs = getNextLoadPositionUs();
boolean isBackedOff = currentLoadableException != null;
boolean loadingOrBackedOff = loader.isLoading() || isBackedOff;
// Update the control with our current state, and determine whether we're the next loader.
boolean nextLoader = loadControl.update(this, downstreamPositionUs, nextLoadPositionUs,
loadingOrBackedOff);
if (isBackedOff) {
long elapsedMillis = now - currentLoadableExceptionTimestamp;
if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) {
currentLoadableException = null;
loader.startLoading(currentLoadable, this);
}
if (loader.isLoading()) {
return;
}
if (loader.isLoading() || !nextLoader || (prepared && enabledTrackCount == 0)) {
long nextLoadPositionUs = getNextLoadPositionUs();
boolean isNext = loadControl.update(this, downstreamPositionUs, nextLoadPositionUs, false);
if (!isNext || (prepared && enabledTrackCount == 0)) {
return;
}
......@@ -681,11 +662,12 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
loadControl.update(this, downstreamPositionUs, -1, false);
return;
}
if (nextLoadable == null) {
return;
}
currentLoadStartTimeMs = now;
currentLoadStartTimeMs = SystemClock.elapsedRealtime();
currentLoadable = nextLoadable;
if (isTsChunk(currentLoadable)) {
TsChunk tsChunk = (TsChunk) currentLoadable;
......@@ -705,6 +687,8 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
currentLoadable.trigger, currentLoadable.format, -1, -1);
}
loader.startLoading(currentLoadable, this);
// Update the load control again to indicate that we're now loading.
loadControl.update(this, downstreamPositionUs, getNextLoadPositionUs(), true);
}
/**
......@@ -728,10 +712,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
return pendingResetPositionUs != NO_RESET_PENDING;
}
private long getRetryDelayMillis(long errorCount) {
return Math.min((errorCount - 1) * 1000, 5000);
}
/* package */ long usToMs(long timeUs) {
return timeUs / 1000;
}
......
......@@ -77,82 +77,100 @@ public final class Loader {
public interface Callback {
/**
* Invoked when loading has been canceled.
* Invoked when a load has been canceled.
*
* @param loadable The loadable whose load has been canceled.
*/
void onLoadCanceled(Loadable loadable);
/**
* Invoked when the data source has been fully loaded.
* Invoked when a load has completed.
*
* @param loadable The loadable whose load has completed.
*/
void onLoadCompleted(Loadable loadable);
/**
* Invoked when the data source is stopped due to an error.
* Invoked when a load encounters an error.
*
* @param loadable The loadable whose load has failed.
* @param loadable The loadable whose load has encountered an error.
* @param exception The error.
* @return The desired retry action. One of {@link Loader#DONT_RETRY}, {@link Loader#RETRY} and
* {@link Loader#RETRY_RESET_ERROR_COUNT}.
*/
void onLoadError(Loadable loadable, IOException exception);
int onLoadError(Loadable loadable, IOException exception);
}
private static final int MSG_END_OF_SOURCE = 0;
private static final int MSG_IO_EXCEPTION = 1;
private static final int MSG_FATAL_ERROR = 2;
public static final int DONT_RETRY = 0;
public static final int RETRY = 1;
public static final int RETRY_RESET_ERROR_COUNT = 2;
private static final int MSG_START = 0;
private static final int MSG_CANCEL = 1;
private static final int MSG_END_OF_SOURCE = 2;
private static final int MSG_IO_EXCEPTION = 3;
private static final int MSG_FATAL_ERROR = 4;
private final ExecutorService downloadExecutorService;
private int minRetryCount;
private LoadTask currentTask;
private boolean loading;
/**
* @param threadName A name for the loader's thread.
* @param minRetryCount The minimum retry count.
*/
public Loader(String threadName) {
public Loader(String threadName, int minRetryCount) {
this.downloadExecutorService = Util.newSingleThreadExecutor(threadName);
this.minRetryCount = minRetryCount;
}
/**
* Invokes {@link #startLoading(Looper, Loadable, Callback)}, using the {@link Looper}
* associated with the calling thread.
* Start loading a {@link Loadable}.
* <p>
* The calling thread must be a {@link Looper} thread, which is the thread on which the
* {@link Callback} will be invoked.
*
* @param loadable The {@link Loadable} to load.
* @param callback A callback to invoke when the load ends.
* @throws IllegalStateException If the calling thread does not have an associated {@link Looper}.
*/
public void startLoading(Loadable loadable, Callback callback) {
Looper myLooper = Looper.myLooper();
Assertions.checkState(myLooper != null);
startLoading(myLooper, loadable, callback);
Looper looper = Looper.myLooper();
Assertions.checkState(looper != null);
new LoadTask(looper, loadable, callback).start(0);
}
/**
* Start loading a {@link Loadable}.
* <p>
* A {@link Loader} instance can only load one {@link Loadable} at a time, and so this method
* must not be called when another load is in progress.
* Whether the {@link Loader} is currently loading a {@link Loadable}.
*
* @param looper The looper of the thread on which the callback should be invoked.
* @param loadable The {@link Loadable} to load.
* @param callback A callback to invoke when the load ends.
* @return Whether the {@link Loader} is currently loading a {@link Loadable}.
*/
public void startLoading(Looper looper, Loadable loadable, Callback callback) {
Assertions.checkState(!loading);
loading = true;
currentTask = new LoadTask(looper, loadable, callback);
downloadExecutorService.submit(currentTask);
public boolean isLoading() {
return currentTask != null;
}
/**
* Whether the {@link Loader} is currently loading a {@link Loadable}.
* Sets the minimum retry count.
*
* @return Whether the {@link Loader} is currently loading a {@link Loadable}.
* @param minRetryCount The minimum retry count.
*/
public boolean isLoading() {
return loading;
public void setMinRetryCount(int minRetryCount) {
this.minRetryCount = minRetryCount;
}
/**
* If the current {@link Loadable} has incurred a number of errors greater than the minimum
* number of retries and if the load is currently backed off, then the most recent error is
* thrown. Else does nothing.
*
* @throws IOException The most recent error encountered by the current {@link Loadable}.
*/
public void maybeThrowError() throws IOException {
if (currentTask != null) {
currentTask.maybeThrowError(minRetryCount);
}
}
/**
......@@ -161,8 +179,7 @@ public final class Loader {
* This method should only be called when a load is in progress.
*/
public void cancelLoading() {
Assertions.checkState(loading);
currentTask.quit();
currentTask.cancel();
}
/**
......@@ -171,7 +188,7 @@ public final class Loader {
* This method should be called when the {@link Loader} is no longer required.
*/
public void release() {
if (loading) {
if (currentTask != null) {
cancelLoading();
}
downloadExecutorService.shutdown();
......@@ -185,6 +202,9 @@ public final class Loader {
private final Loadable loadable;
private final Loader.Callback callback;
private IOException currentError;
private int errorCount;
private volatile Thread executorThread;
public LoadTask(Looper looper, Loadable loadable, Loader.Callback callback) {
......@@ -193,10 +213,32 @@ public final class Loader {
this.callback = callback;
}
public void quit() {
loadable.cancelLoad();
if (executorThread != null) {
executorThread.interrupt();
public void maybeThrowError(int minRetryCount) throws IOException {
if (currentError != null && errorCount > minRetryCount) {
throw currentError;
}
}
public void start(long delayMillis) {
Assertions.checkState(currentTask == null);
currentTask = this;
if (delayMillis > 0) {
sendEmptyMessageDelayed(MSG_START, delayMillis);
} else {
submitToExecutor();
}
}
public void cancel() {
currentError = null;
if (hasMessages(MSG_START)) {
removeMessages(MSG_START);
sendEmptyMessage(MSG_CANCEL);
} else {
loadable.cancelLoad();
if (executorThread != null) {
executorThread.interrupt();
}
}
}
......@@ -235,29 +277,49 @@ public final class Loader {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG_START) {
submitToExecutor();
return;
}
if (msg.what == MSG_FATAL_ERROR) {
throw (Error) msg.obj;
}
onFinished();
finish();
if (loadable.isLoadCanceled()) {
callback.onLoadCanceled(loadable);
return;
}
switch (msg.what) {
case MSG_CANCEL:
callback.onLoadCanceled(loadable);
break;
case MSG_END_OF_SOURCE:
callback.onLoadCompleted(loadable);
break;
case MSG_IO_EXCEPTION:
callback.onLoadError(loadable, (IOException) msg.obj);
currentError = (IOException) msg.obj;
int retryAction = callback.onLoadError(loadable, currentError);
if (retryAction != DONT_RETRY) {
errorCount = retryAction == RETRY_RESET_ERROR_COUNT ? 1 : errorCount + 1;
start(getRetryDelayMillis());
}
break;
}
}
private void onFinished() {
loading = false;
private void submitToExecutor() {
currentError = null;
downloadExecutorService.submit(currentTask);
}
private void finish() {
currentTask = null;
}
private long getRetryDelayMillis() {
return Math.min((errorCount - 1) * 1000, 5000);
}
}
}
......@@ -81,10 +81,6 @@ public class ManifestFetcher<T> implements Loader.Callback {
private UriLoadable<T> currentLoadable;
private long currentLoadStartTimestamp;
private int loadExceptionCount;
private long loadExceptionTimestamp;
private ManifestIOException loadException;
private volatile T manifest;
private volatile long manifestLoadStartTimestamp;
private volatile long manifestLoadCompleteTimestamp;
......@@ -162,21 +158,20 @@ public class ManifestFetcher<T> implements Loader.Callback {
* manifest.
*/
public void maybeThrowError() throws ManifestIOException {
// Don't throw an exception until at least 1 retry attempt has been made.
if (loadException == null || loadExceptionCount <= 1) {
return;
if (loader != null) {
try {
loader.maybeThrowError();
} catch (IOException e) {
throw new ManifestIOException(e);
}
}
throw loadException;
}
/**
* Enables refresh functionality.
*/
public void enable() {
if (enabledCount++ == 0) {
loadExceptionCount = 0;
loadException = null;
}
enabledCount++;
}
/**
......@@ -195,20 +190,16 @@ public class ManifestFetcher<T> implements Loader.Callback {
* Should be invoked repeatedly by callers who require an updated manifest.
*/
public void requestRefresh() {
if (loadException != null && SystemClock.elapsedRealtime()
< (loadExceptionTimestamp + getRetryDelayMillis(loadExceptionCount))) {
// The previous load failed, and it's too soon to try again.
return;
}
if (loader == null) {
loader = new Loader("manifestLoader");
loader = new Loader("manifestLoader", 1);
}
if (!loader.isLoading()) {
currentLoadable = new UriLoadable<>(manifestUri, dataSource, parser);
currentLoadStartTimestamp = SystemClock.elapsedRealtime();
loader.startLoading(currentLoadable, this);
notifyManifestRefreshStarted();
if (loader.isLoading()) {
return;
}
currentLoadable = new UriLoadable<>(manifestUri, dataSource, parser);
currentLoadStartTimestamp = SystemClock.elapsedRealtime();
loader.startLoading(currentLoadable, this);
notifyManifestRefreshStarted();
}
@Override
......@@ -221,8 +212,6 @@ public class ManifestFetcher<T> implements Loader.Callback {
manifest = currentLoadable.getResult();
manifestLoadStartTimestamp = currentLoadStartTimestamp;
manifestLoadCompleteTimestamp = SystemClock.elapsedRealtime();
loadExceptionCount = 0;
loadException = null;
if (manifest instanceof RedirectingManifest) {
RedirectingManifest redirectingManifest = (RedirectingManifest) manifest;
......@@ -241,21 +230,14 @@ public class ManifestFetcher<T> implements Loader.Callback {
}
@Override
public void onLoadError(Loadable loadable, IOException exception) {
public int onLoadError(Loadable loadable, IOException exception) {
if (currentLoadable != loadable) {
// Stale event.
return;
return Loader.DONT_RETRY;
}
loadExceptionCount++;
loadExceptionTimestamp = SystemClock.elapsedRealtime();
loadException = new ManifestIOException(exception);
notifyManifestError(loadException);
}
private long getRetryDelayMillis(long errorCount) {
return Math.min((errorCount - 1) * 1000, 5000);
notifyManifestError(new ManifestIOException(exception));
return Loader.RETRY;
}
private void notifyManifestRefreshStarted() {
......
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