Commit e74fc80a by olly Committed by Oliver Woodman

Loader improvements.

This change moves generally useful functionality (load timing
and fatal error propagation) inside of Loader, so that callers
don't have to duplicate effort.

The change also makes use of generics so that the callback
receives a Loadable with a more specific type.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=123304369
parent db79f798
......@@ -31,8 +31,8 @@ import java.util.List;
/**
* A {@link SampleSource} that loads the data at a given {@link Uri} as a single sample.
*/
public final class SingleSampleSource implements SampleSource, TrackStream, Loader.Callback,
Loadable {
public final class SingleSampleSource implements SampleSource, TrackStream,
Loader.Callback<SingleSampleSource>, Loadable {
/**
* Interface definition for a callback to be notified of {@link SingleSampleSource} events.
......@@ -65,7 +65,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
private final Uri uri;
private final DataSource dataSource;
private final Loader loader;
private final Loader<SingleSampleSource> loader;
private final Format format;
private final long durationUs;
private final TrackGroupArray tracks;
......@@ -99,7 +99,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
this.eventHandler = eventHandler;
this.eventListener = eventListener;
this.eventSourceId = eventSourceId;
loader = new Loader("Loader:SingleSampleSource", minLoadableRetryCount);
loader = new Loader<>("Loader:SingleSampleSource", minLoadableRetryCount);
tracks = new TrackGroupArray(new TrackGroup(format));
sampleData = new byte[INITIAL_SAMPLE_SIZE];
}
......@@ -214,17 +214,17 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
// Loader.Callback implementation.
@Override
public void onLoadCompleted(Loadable loadable) {
public void onLoadCompleted(SingleSampleSource loadable, long elapsedMs) {
loadingFinished = true;
}
@Override
public void onLoadCanceled(Loadable loadable) {
public void onLoadCanceled(SingleSampleSource loadable, long elapsedMs) {
maybeStartLoading();
}
@Override
public int onLoadError(Loadable loadable, IOException e) {
public int onLoadError(SingleSampleSource loadable, long elapsedMs, IOException e) {
notifyLoadError(e);
return Loader.RETRY;
}
......
......@@ -24,7 +24,6 @@ import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.chunk.ChunkTrackStreamEventListener.EventDispatcher;
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable;
import com.google.android.exoplayer.util.Assertions;
import android.os.Handler;
......@@ -38,9 +37,9 @@ import java.util.List;
/**
* A {@link TrackStream} that loads media in {@link Chunk}s, obtained from a {@link ChunkSource}.
*/
public class ChunkTrackStream implements TrackStream, Loader.Callback {
public class ChunkTrackStream implements TrackStream, Loader.Callback<Chunk> {
private final Loader loader;
private final Loader<Chunk> loader;
private final ChunkSource chunkSource;
private final LinkedList<BaseMediaChunk> mediaChunks;
private final List<BaseMediaChunk> readOnlyMediaChunks;
......@@ -57,8 +56,6 @@ public class ChunkTrackStream implements TrackStream, Loader.Callback {
private long lastSeekPositionUs;
private long pendingResetPositionUs;
private Chunk currentLoadable;
private long currentLoadStartTimeMs;
private boolean loadingFinished;
private boolean released;
......@@ -79,7 +76,7 @@ public class ChunkTrackStream implements TrackStream, Loader.Callback {
ChunkTrackStreamEventListener eventListener, int eventSourceId, int minLoadableRetryCount) {
this.chunkSource = chunkSource;
this.loadControl = loadControl;
loader = new Loader("Loader:ChunkTrackStream", minLoadableRetryCount);
loader = new Loader<>("Loader:ChunkTrackStream", minLoadableRetryCount);
eventDispatcher = new EventDispatcher(eventHandler, eventListener, eventSourceId);
nextChunkHolder = new ChunkHolder();
mediaChunks = new LinkedList<>();
......@@ -124,7 +121,7 @@ public class ChunkTrackStream implements TrackStream, Loader.Callback {
} else {
long bufferedPositionUs = downstreamPositionUs;
BaseMediaChunk lastMediaChunk = mediaChunks.getLast();
BaseMediaChunk lastCompletedMediaChunk = lastMediaChunk != currentLoadable ? lastMediaChunk
BaseMediaChunk lastCompletedMediaChunk = lastMediaChunk.isLoadCompleted() ? lastMediaChunk
: mediaChunks.size() > 1 ? mediaChunks.get(mediaChunks.size() - 2) : null;
if (lastCompletedMediaChunk != null) {
bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs);
......@@ -210,26 +207,24 @@ public class ChunkTrackStream implements TrackStream, Loader.Callback {
// Loader.Callback implementation.
@Override
public void onLoadCompleted(Loadable loadable) {
public void onLoadCompleted(Chunk loadable, long elapsedMs) {
long now = SystemClock.elapsedRealtime();
long loadDurationMs = now - currentLoadStartTimeMs;
chunkSource.onChunkLoadCompleted(currentLoadable);
if (isMediaChunk(currentLoadable)) {
BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable;
eventDispatcher.loadCompleted(currentLoadable.bytesLoaded(), mediaChunk.type,
chunkSource.onChunkLoadCompleted(loadable);
if (isMediaChunk(loadable)) {
BaseMediaChunk mediaChunk = (BaseMediaChunk) loadable;
eventDispatcher.loadCompleted(loadable.bytesLoaded(), mediaChunk.type,
mediaChunk.trigger, mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs, now,
loadDurationMs);
elapsedMs);
} else {
eventDispatcher.loadCompleted(currentLoadable.bytesLoaded(), currentLoadable.type,
currentLoadable.trigger, currentLoadable.format, -1, -1, now, loadDurationMs);
eventDispatcher.loadCompleted(loadable.bytesLoaded(), loadable.type, loadable.trigger,
loadable.format, -1, -1, now, elapsedMs);
}
clearCurrentLoadable();
maybeStartLoading();
}
@Override
public void onLoadCanceled(Loadable loadable) {
eventDispatcher.loadCanceled(currentLoadable.bytesLoaded());
public void onLoadCanceled(Chunk loadable, long elapsedMs) {
eventDispatcher.loadCanceled(loadable.bytesLoaded());
if (!released) {
restartFrom(pendingResetPositionUs);
} else {
......@@ -239,20 +234,19 @@ public class ChunkTrackStream implements TrackStream, Loader.Callback {
}
@Override
public int onLoadError(Loadable loadable, IOException e) {
long bytesLoaded = currentLoadable.bytesLoaded();
boolean isMediaChunk = isMediaChunk(currentLoadable);
public int onLoadError(Chunk loadable, long elapsedMs, IOException e) {
long bytesLoaded = loadable.bytesLoaded();
boolean isMediaChunk = isMediaChunk(loadable);
boolean cancelable = !isMediaChunk || bytesLoaded == 0 || mediaChunks.size() > 1;
if (chunkSource.onChunkLoadError(currentLoadable, cancelable, e)) {
if (chunkSource.onChunkLoadError(loadable, cancelable, e)) {
if (isMediaChunk) {
BaseMediaChunk removed = mediaChunks.removeLast();
Assertions.checkState(removed == currentLoadable);
Assertions.checkState(removed == loadable);
sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex());
if (mediaChunks.isEmpty()) {
pendingResetPositionUs = lastSeekPositionUs;
}
}
clearCurrentLoadable();
eventDispatcher.loadError(e);
eventDispatcher.loadCanceled(bytesLoaded);
maybeStartLoading();
......@@ -279,11 +273,6 @@ public class ChunkTrackStream implements TrackStream, Loader.Callback {
private void clearState() {
sampleQueue.clear();
mediaChunks.clear();
clearCurrentLoadable();
}
private void clearCurrentLoadable() {
currentLoadable = null;
}
private void maybeStartLoading() {
......@@ -304,7 +293,7 @@ public class ChunkTrackStream implements TrackStream, Loader.Callback {
pendingResetPositionUs != C.UNSET_TIME_US ? pendingResetPositionUs : downstreamPositionUs,
nextChunkHolder);
boolean endOfStream = nextChunkHolder.endOfStream;
Chunk nextLoadable = nextChunkHolder.chunk;
Chunk loadable = nextChunkHolder.chunk;
nextChunkHolder.clear();
if (endOfStream) {
......@@ -313,24 +302,22 @@ public class ChunkTrackStream implements TrackStream, Loader.Callback {
return;
}
if (nextLoadable == null) {
if (loadable == null) {
return;
}
currentLoadStartTimeMs = now;
currentLoadable = nextLoadable;
if (isMediaChunk(currentLoadable)) {
if (isMediaChunk(loadable)) {
pendingResetPositionUs = C.UNSET_TIME_US;
BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable;
BaseMediaChunk mediaChunk = (BaseMediaChunk) loadable;
mediaChunk.init(sampleQueue);
mediaChunks.add(mediaChunk);
eventDispatcher.loadStarted(mediaChunk.dataSpec.length, mediaChunk.type, mediaChunk.trigger,
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs);
} else {
eventDispatcher.loadStarted(currentLoadable.dataSpec.length, currentLoadable.type,
currentLoadable.trigger, currentLoadable.format, -1, -1);
eventDispatcher.loadStarted(loadable.dataSpec.length, loadable.type, loadable.trigger,
loadable.format, -1, -1);
}
loader.startLoading(currentLoadable, this);
loader.startLoading(loadable, this);
// Update the load control again to indicate that we're now loading.
loadControl.update(this, downstreamPositionUs, getNextLoadPositionUs(), true);
}
......
......@@ -39,6 +39,7 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
private volatile int bytesLoaded;
private volatile boolean loadCanceled;
private volatile boolean loadCompleted;
/**
* @param dataSource A {@link DataSource} for loading the data.
......@@ -63,6 +64,11 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
}
@Override
public boolean isLoadCompleted() {
return loadCompleted;
}
@Override
public final long bytesLoaded() {
return bytesLoaded;
}
......@@ -112,6 +118,7 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe
} finally {
dataSource.close();
}
loadCompleted = true;
}
}
......@@ -56,8 +56,16 @@ public abstract class MediaChunk extends Chunk {
this.chunkIndex = chunkIndex;
}
/**
* Returns the next chunk index.
*/
public final int getNextChunkIndex() {
return chunkIndex + 1;
}
/**
* Returns whether the chunk has been fully loaded.
*/
public abstract boolean isLoadCompleted();
}
......@@ -35,6 +35,7 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
private volatile int bytesLoaded;
private volatile boolean loadCanceled;
private volatile boolean loadCompleted;
/**
* @param dataSource A {@link DataSource} for loading the data.
......@@ -53,6 +54,11 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
}
@Override
public boolean isLoadCompleted() {
return loadCompleted;
}
@Override
public long bytesLoaded() {
return bytesLoaded;
}
......@@ -93,6 +99,7 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
} finally {
dataSource.close();
}
loadCompleted = true;
}
}
......@@ -18,7 +18,6 @@ package com.google.android.exoplayer.dash.mpd;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable;
import com.google.android.exoplayer.upstream.UriLoadable;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util;
......@@ -39,7 +38,7 @@ import java.util.concurrent.CancellationException;
/**
* Resolves a {@link UtcTimingElement}.
*/
public final class UtcTimingElementResolver implements Loader.Callback {
public final class UtcTimingElementResolver implements Loader.Callback<UriLoadable<Long>> {
/**
* Callback for timing element resolution.
......@@ -70,8 +69,7 @@ public final class UtcTimingElementResolver implements Loader.Callback {
private final long timingElementElapsedRealtime;
private final UtcTimingCallback callback;
private Loader singleUseLoader;
private UriLoadable<Long> singleUseLoadable;
private Loader<UriLoadable<Long>> singleUseLoader;
/**
* Resolves a {@link UtcTimingElement}.
......@@ -123,25 +121,26 @@ public final class UtcTimingElementResolver implements Loader.Callback {
}
private void resolveHttp(UriLoadable.Parser<Long> parser) {
singleUseLoader = new Loader("utctiming", 0);
singleUseLoadable = new UriLoadable<>(Uri.parse(timingElement.value), dataSource, parser);
singleUseLoader.startLoading(singleUseLoadable, this);
singleUseLoader = new Loader<>("Loader:UtcTiming", 0);
singleUseLoader.startLoading(
new UriLoadable<>(Uri.parse(timingElement.value), dataSource, parser), this);
}
@Override
public void onLoadCanceled(Loadable loadable) {
onLoadError(loadable, new IOException("Load cancelled", new CancellationException()));
public void onLoadCompleted(UriLoadable<Long> loadable, long elapsedMs) {
releaseLoader();
long elapsedRealtimeOffset = loadable.getResult() - SystemClock.elapsedRealtime();
callback.onTimestampResolved(timingElement, elapsedRealtimeOffset);
}
@Override
public void onLoadCompleted(Loadable loadable) {
releaseLoader();
long elapsedRealtimeOffset = singleUseLoadable.getResult() - SystemClock.elapsedRealtime();
callback.onTimestampResolved(timingElement, elapsedRealtimeOffset);
public void onLoadCanceled(UriLoadable<Long> loadable, long elapsedMs) {
onLoadError(loadable, elapsedMs,
new IOException("Load cancelled", new CancellationException()));
}
@Override
public int onLoadError(Loadable loadable, IOException exception) {
public int onLoadError(UriLoadable<Long> loadable, long elapsedMs, IOException exception) {
releaseLoader();
callback.onTimestampError(timingElement, exception);
return Loader.DONT_RETRY;
......
......@@ -67,7 +67,8 @@ import java.util.List;
* constructor. When reading a new stream, the first {@link Extractor} that returns {@code true}
* from {@link Extractor#sniff(ExtractorInput)} will be used.
*/
public final class ExtractorSampleSource implements SampleSource, ExtractorOutput, Loader.Callback {
public final class ExtractorSampleSource implements SampleSource, ExtractorOutput,
Loader.Callback<Loadable> {
/**
* Interface definition for a callback to be notified of {@link ExtractorSampleSource} events.
......@@ -111,7 +112,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
// Lazily initialized default extractor classes in priority order.
private static List<Class<? extends Extractor>> defaultExtractorClasses;
private final Loader loader;
private final Loader<Loadable> loader;
private final ExtractorHolder extractorHolder;
private final Allocator allocator;
private final int requestedBufferSize;
......@@ -139,7 +140,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private long pendingResetPositionUs;
private ExtractingLoadable loadable;
private IOException fatalException;
private int extractedSamplesCountAtStartOfLoad;
private boolean loadingFinished;
......@@ -233,7 +233,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
// Assume on-demand until we know otherwise.
int initialMinRetryCount = minLoadableRetryCount == MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA
? DEFAULT_MIN_LOADABLE_RETRY_COUNT_ON_DEMAND : minLoadableRetryCount;
loader = new Loader("Loader:ExtractorSampleSource", initialMinRetryCount);
loader = new Loader<>("Loader:ExtractorSampleSource", initialMinRetryCount);
extractorHolder = new ExtractorHolder(extractors, this);
pendingResetPositionUs = C.UNSET_TIME_US;
sampleQueues = new DefaultTrackOutput[0];
......@@ -476,9 +476,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
}
/* package */ void maybeThrowError() throws IOException {
if (fatalException != null) {
throw fatalException;
}
loader.maybeThrowError();
}
......@@ -493,12 +490,12 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
// Loader.Callback implementation.
@Override
public void onLoadCompleted(Loadable loadable) {
public void onLoadCompleted(Loadable loadable, long elapsedMs) {
loadingFinished = true;
}
@Override
public void onLoadCanceled(Loadable loadable) {
public void onLoadCanceled(Loadable loadable, long elapsedMs) {
if (enabledTrackCount > 0) {
restartFrom(pendingResetPositionUs);
} else {
......@@ -508,11 +505,10 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
}
@Override
public int onLoadError(Loadable ignored, IOException e) {
public int onLoadError(Loadable loadable, long elapsedMs, IOException e) {
notifyLoadError(e);
if (isLoadableExceptionFatal(e)) {
fatalException = e;
return Loader.DONT_RETRY;
return Loader.DONT_RETRY_FATAL;
}
int extractedSamplesCount = getExtractedSamplesCount();
boolean madeProgress = extractedSamplesCount > extractedSamplesCountAtStartOfLoad;
......@@ -640,7 +636,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private void clearState() {
clearSampleQueues();
loadable = null;
fatalException = null;
}
private void clearSampleQueues() {
......
......@@ -47,6 +47,7 @@ import java.io.IOException;
private int bytesLoaded;
private volatile boolean loadCanceled;
private volatile boolean loadCompleted;
/**
* @param dataSource A {@link DataSource} for loading the data.
......@@ -95,6 +96,11 @@ import java.io.IOException;
}
@Override
public boolean isLoadCompleted() {
return loadCompleted;
}
@Override
public long bytesLoaded() {
return bytesLoaded;
}
......@@ -143,6 +149,7 @@ import java.io.IOException;
} finally {
dataSource.close();
}
loadCompleted = true;
}
// Private methods
......
......@@ -32,7 +32,6 @@ import com.google.android.exoplayer.extractor.DefaultTrackOutput;
import com.google.android.exoplayer.extractor.ExtractorOutput;
import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
......@@ -48,7 +47,7 @@ import java.util.List;
* Loads {@link HlsMediaChunk}s obtained from a {@link HlsChunkSource}, and provides
* {@link TrackStream}s from which the loaded media can be consumed.
*/
/* package */ final class HlsTrackStreamWrapper implements Loader.Callback, ExtractorOutput {
/* package */ final class HlsTrackStreamWrapper implements Loader.Callback<Chunk>, ExtractorOutput {
/**
* The default minimum number of times to retry loading data prior to failing.
......@@ -60,7 +59,7 @@ import java.util.List;
private static final int PRIMARY_TYPE_AUDIO = 2;
private static final int PRIMARY_TYPE_VIDEO = 3;
private final Loader loader;
private final Loader<Chunk> loader;
private final HlsChunkSource chunkSource;
private final SparseArray<DefaultTrackOutput> sampleQueues;
private final LinkedList<HlsMediaChunk> mediaChunks;
......@@ -88,8 +87,6 @@ import java.util.List;
private long lastSeekPositionUs;
private long pendingResetPositionUs;
private Chunk currentLoadable;
private long currentLoadStartTimeMs;
private boolean loadingFinished;
/**
......@@ -135,7 +132,7 @@ import java.util.List;
this.chunkSource = chunkSource;
this.loadControl = loadControl;
this.bufferSizeContribution = bufferSizeContribution;
loader = new Loader("Loader:HLS", minLoadableRetryCount);
loader = new Loader<>("Loader:HLS", minLoadableRetryCount);
eventDispatcher = new EventDispatcher(eventHandler, eventListener, eventSourceId);
nextChunkHolder = new ChunkHolder();
sampleQueues = new SparseArray<>();
......@@ -264,7 +261,7 @@ import java.util.List;
} else {
long bufferedPositionUs = downstreamPositionUs;
HlsMediaChunk lastMediaChunk = mediaChunks.getLast();
HlsMediaChunk lastCompletedMediaChunk = lastMediaChunk != currentLoadable ? lastMediaChunk
HlsMediaChunk lastCompletedMediaChunk = lastMediaChunk.isLoadCompleted() ? lastMediaChunk
: mediaChunks.size() > 1 ? mediaChunks.get(mediaChunks.size() - 2) : null;
if (lastCompletedMediaChunk != null) {
bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs);
......@@ -324,26 +321,23 @@ import java.util.List;
// Loader.Callback implementation.
@Override
public void onLoadCompleted(Loadable loadable) {
public void onLoadCompleted(Chunk loadable, long elapsedMs) {
long now = SystemClock.elapsedRealtime();
long loadDurationMs = now - currentLoadStartTimeMs;
chunkSource.onChunkLoadCompleted(currentLoadable);
if (isMediaChunk(currentLoadable)) {
HlsMediaChunk mediaChunk = (HlsMediaChunk) currentLoadable;
eventDispatcher.loadCompleted(currentLoadable.bytesLoaded(), mediaChunk.type,
mediaChunk.trigger, mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs, now,
loadDurationMs);
chunkSource.onChunkLoadCompleted(loadable);
if (isMediaChunk(loadable)) {
HlsMediaChunk mediaChunk = (HlsMediaChunk) loadable;
eventDispatcher.loadCompleted(loadable.bytesLoaded(), mediaChunk.type, mediaChunk.trigger,
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs, now, elapsedMs);
} else {
eventDispatcher.loadCompleted(currentLoadable.bytesLoaded(), currentLoadable.type,
currentLoadable.trigger, currentLoadable.format, -1, -1, now, loadDurationMs);
eventDispatcher.loadCompleted(loadable.bytesLoaded(), loadable.type, loadable.trigger,
loadable.format, -1, -1, now, elapsedMs);
}
clearCurrentLoadable();
maybeStartLoading();
}
@Override
public void onLoadCanceled(Loadable loadable) {
eventDispatcher.loadCanceled(currentLoadable.bytesLoaded());
public void onLoadCanceled(Chunk loadable, long elapsedMs) {
eventDispatcher.loadCanceled(loadable.bytesLoaded());
if (enabledTrackCount > 0) {
restartFrom(pendingResetPositionUs);
} else {
......@@ -353,19 +347,18 @@ import java.util.List;
}
@Override
public int onLoadError(Loadable loadable, IOException e) {
long bytesLoaded = currentLoadable.bytesLoaded();
boolean isMediaChunk = isMediaChunk(currentLoadable);
public int onLoadError(Chunk loadable, long elapsedMs, IOException e) {
long bytesLoaded = loadable.bytesLoaded();
boolean isMediaChunk = isMediaChunk(loadable);
boolean cancelable = !isMediaChunk || bytesLoaded == 0;
if (chunkSource.onChunkLoadError(currentLoadable, cancelable, e)) {
if (chunkSource.onChunkLoadError(loadable, cancelable, e)) {
if (isMediaChunk) {
HlsMediaChunk removed = mediaChunks.removeLast();
Assertions.checkState(removed == currentLoadable);
Assertions.checkState(removed == loadable);
if (mediaChunks.isEmpty()) {
pendingResetPositionUs = lastSeekPositionUs;
}
}
clearCurrentLoadable();
eventDispatcher.loadError(e);
eventDispatcher.loadCanceled(bytesLoaded);
maybeStartLoading();
......@@ -590,11 +583,6 @@ import java.util.List;
sampleQueues.valueAt(i).clear();
}
mediaChunks.clear();
clearCurrentLoadable();
}
private void clearCurrentLoadable() {
currentLoadable = null;
}
private void maybeStartLoading() {
......@@ -608,7 +596,7 @@ import java.util.List;
pendingResetPositionUs != C.UNSET_TIME_US ? pendingResetPositionUs : downstreamPositionUs,
nextChunkHolder);
boolean endOfStream = nextChunkHolder.endOfStream;
Chunk nextLoadable = nextChunkHolder.chunk;
Chunk loadable = nextChunkHolder.chunk;
nextChunkHolder.clear();
if (endOfStream) {
......@@ -619,24 +607,22 @@ import java.util.List;
return;
}
if (nextLoadable == null) {
if (loadable == null) {
return;
}
currentLoadStartTimeMs = SystemClock.elapsedRealtime();
currentLoadable = nextLoadable;
if (isMediaChunk(currentLoadable)) {
if (isMediaChunk(loadable)) {
pendingResetPositionUs = C.UNSET_TIME_US;
HlsMediaChunk mediaChunk = (HlsMediaChunk) currentLoadable;
HlsMediaChunk mediaChunk = (HlsMediaChunk) loadable;
mediaChunk.init(this);
mediaChunks.addLast(mediaChunk);
eventDispatcher.loadStarted(mediaChunk.dataSpec.length, mediaChunk.type, mediaChunk.trigger,
mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs);
} else {
eventDispatcher.loadStarted(currentLoadable.dataSpec.length, currentLoadable.type,
currentLoadable.trigger, currentLoadable.format, -1, -1);
eventDispatcher.loadStarted(loadable.dataSpec.length, loadable.type, loadable.trigger,
loadable.format, -1, -1);
}
loader.startLoading(currentLoadable, this);
loader.startLoading(loadable, this);
if (prepared) {
// Update the load control again to indicate that we're now loading.
loadControl.update(this, downstreamPositionUs, getNextLoadPositionUs(), true);
......
......@@ -23,6 +23,7 @@ import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import java.io.IOException;
......@@ -31,7 +32,7 @@ import java.util.concurrent.ExecutorService;
/**
* Manages the background loading of {@link Loadable}s.
*/
public final class Loader {
public final class Loader<T extends Loader.Loadable> {
/**
* Thrown when an unexpected exception is encountered during loading.
......@@ -74,37 +75,42 @@ public final class Loader {
/**
* Interface definition for a callback to be notified of {@link Loader} events.
*/
public interface Callback {
public interface Callback<T extends Loadable> {
/**
* Invoked when a load has been canceled.
*
* @param loadable The loadable whose load has been canceled.
* @param elapsedMs The elapsed time in milliseconds since loading started.
*/
void onLoadCanceled(Loadable loadable);
void onLoadCanceled(T loadable, long elapsedMs);
/**
* Invoked when a load has completed.
*
* @param loadable The loadable whose load has completed.
* @param elapsedMs The elapsed time in milliseconds since loading started.
*/
void onLoadCompleted(Loadable loadable);
void onLoadCompleted(T loadable, long elapsedMs);
/**
* Invoked when a load encounters an error.
*
* @param loadable The loadable whose load has encountered an error.
* @param elapsedMs The elapsed time in milliseconds since loading started.
* @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}.
* @return The desired retry action. One of {@link Loader#RETRY},
* {@link Loader#RETRY_RESET_ERROR_COUNT}, {@link Loader#DONT_RETRY} and
* {@link Loader#DONT_RETRY_FATAL}.
*/
int onLoadError(Loadable loadable, IOException exception);
int onLoadError(T loadable, long elapsedMs, IOException exception);
}
public static final int DONT_RETRY = 0;
public static final int RETRY = 1;
public static final int RETRY_RESET_ERROR_COUNT = 2;
public static final int RETRY = 0;
public static final int RETRY_RESET_ERROR_COUNT = 1;
public static final int DONT_RETRY = 2;
public static final int DONT_RETRY_FATAL = 3;
private static final int MSG_START = 0;
private static final int MSG_CANCEL = 1;
......@@ -116,6 +122,7 @@ public final class Loader {
private int minRetryCount;
private LoadTask currentTask;
private IOException fatalError;
/**
* @param threadName A name for the loader's thread.
......@@ -136,7 +143,7 @@ public final class Loader {
* @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) {
public void startLoading(T loadable, Callback<T> callback) {
Looper looper = Looper.myLooper();
Assertions.checkState(looper != null);
new LoadTask(looper, loadable, callback).start(0);
......@@ -161,14 +168,16 @@ public final class Loader {
}
/**
* 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.
* If a fatal error has been encountered, or 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 an error is thrown. Else does nothing.
*
* @throws IOException The most recent error encountered by the current {@link Loadable}.
* @throws IOException The error.
*/
public void maybeThrowError() throws IOException {
if (currentTask != null) {
if (fatalError != null) {
throw fatalError;
} else if (currentTask != null) {
currentTask.maybeThrowError(minRetryCount);
}
}
......@@ -199,18 +208,20 @@ public final class Loader {
private static final String TAG = "LoadTask";
private final Loadable loadable;
private final Loader.Callback callback;
private final T loadable;
private final Loader.Callback<T> callback;
private final long startTimeMs;
private IOException currentError;
private int errorCount;
private volatile Thread executorThread;
public LoadTask(Looper looper, Loadable loadable, Loader.Callback callback) {
public LoadTask(Looper looper, T loadable, Loader.Callback<T> callback) {
super(looper);
this.loadable = loadable;
this.callback = callback;
this.startTimeMs = SystemClock.elapsedRealtime();
}
public void maybeThrowError(int minRetryCount) throws IOException {
......@@ -285,21 +296,24 @@ public final class Loader {
throw (Error) msg.obj;
}
finish();
long elapsedMs = SystemClock.elapsedRealtime() - startTimeMs;
if (loadable.isLoadCanceled()) {
callback.onLoadCanceled(loadable);
callback.onLoadCanceled(loadable, elapsedMs);
return;
}
switch (msg.what) {
case MSG_CANCEL:
callback.onLoadCanceled(loadable);
callback.onLoadCanceled(loadable, elapsedMs);
break;
case MSG_END_OF_SOURCE:
callback.onLoadCompleted(loadable);
callback.onLoadCompleted(loadable, elapsedMs);
break;
case MSG_IO_EXCEPTION:
currentError = (IOException) msg.obj;
int retryAction = callback.onLoadError(loadable, currentError);
if (retryAction != DONT_RETRY) {
int retryAction = callback.onLoadError(loadable, elapsedMs, currentError);
if (retryAction == DONT_RETRY_FATAL) {
fatalError = currentError;
} else if (retryAction != DONT_RETRY) {
errorCount = retryAction == RETRY_RESET_ERROR_COUNT ? 1 : errorCount + 1;
start(getRetryDelayMillis());
}
......
......@@ -17,7 +17,6 @@ package com.google.android.exoplayer.util;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable;
import com.google.android.exoplayer.upstream.UriLoadable;
import android.net.Uri;
......@@ -32,7 +31,7 @@ import java.io.IOException;
*
* @param <T> The type of manifest.
*/
public class ManifestFetcher<T> implements Loader.Callback {
public class ManifestFetcher<T> implements Loader.Callback<UriLoadable<T>> {
/**
* Thrown when an error occurs trying to fetch a manifest.
......@@ -69,7 +68,7 @@ public class ManifestFetcher<T> implements Loader.Callback {
}
private final Loader loader;
private final Loader<UriLoadable<T>> loader;
private final UriLoadable.Parser<T> parser;
private final DataSource dataSource;
private final Handler eventHandler;
......@@ -77,7 +76,6 @@ public class ManifestFetcher<T> implements Loader.Callback {
private volatile Uri manifestUri;
private UriLoadable<T> currentLoadable;
private long currentLoadStartTimestamp;
private volatile T manifest;
......@@ -108,7 +106,7 @@ public class ManifestFetcher<T> implements Loader.Callback {
this.dataSource = dataSource;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
loader = new Loader("Loader:ManifestFetcher", 1);
loader = new Loader<>("Loader:ManifestFetcher", 1);
}
/**
......@@ -174,9 +172,8 @@ public class ManifestFetcher<T> implements Loader.Callback {
if (loader.isLoading()) {
return;
}
currentLoadable = new UriLoadable<>(manifestUri, dataSource, parser);
currentLoadStartTimestamp = SystemClock.elapsedRealtime();
loader.startLoading(currentLoadable, this);
loader.startLoading(new UriLoadable<>(manifestUri, dataSource, parser), this);
notifyManifestRefreshStarted();
}
......@@ -192,8 +189,8 @@ public class ManifestFetcher<T> implements Loader.Callback {
// Loadable.Callback implementation.
@Override
public void onLoadCompleted(Loadable loadable) {
manifest = currentLoadable.getResult();
public void onLoadCompleted(UriLoadable<T> loadable, long elapsedMs) {
manifest = loadable.getResult();
manifestLoadStartTimestamp = currentLoadStartTimestamp;
manifestLoadCompleteTimestamp = SystemClock.elapsedRealtime();
if (manifest instanceof RedirectingManifest) {
......@@ -207,12 +204,12 @@ public class ManifestFetcher<T> implements Loader.Callback {
}
@Override
public void onLoadCanceled(Loadable loadable) {
public void onLoadCanceled(UriLoadable<T> loadable, long elapsedMs) {
// Do nothing.
}
@Override
public int onLoadError(Loadable loadable, IOException exception) {
public int onLoadError(UriLoadable<T> loadable, long elapsedMs, IOException exception) {
notifyManifestError(new ManifestIOException(exception));
return Loader.RETRY;
}
......
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