Enhance Loader API.

This commit is contained in:
Oliver Woodman 2014-09-19 18:36:12 +01:00
parent ce5eea72d2
commit c4e1c3543c
2 changed files with 47 additions and 23 deletions

View File

@ -24,6 +24,7 @@ import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackInfo; import com.google.android.exoplayer.TrackInfo;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.upstream.Loader; 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.Assertions;
import android.os.Handler; import android.os.Handler;
@ -39,7 +40,7 @@ import java.util.List;
* A {@link SampleSource} that loads media in {@link Chunk}s, which are themselves obtained from a * A {@link SampleSource} that loads media in {@link Chunk}s, which are themselves obtained from a
* {@link ChunkSource}. * {@link ChunkSource}.
*/ */
public class ChunkSampleSource implements SampleSource, Loader.Listener { public class ChunkSampleSource implements SampleSource, Loader.Callback {
/** /**
* Interface definition for a callback to be notified of {@link ChunkSampleSource} events. * Interface definition for a callback to be notified of {@link ChunkSampleSource} events.
@ -199,7 +200,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
@Override @Override
public boolean prepare() { public boolean prepare() {
Assertions.checkState(state == STATE_UNPREPARED); Assertions.checkState(state == STATE_UNPREPARED);
loader = new Loader("Loader:" + chunkSource.getTrackInfo().mimeType, this); loader = new Loader("Loader:" + chunkSource.getTrackInfo().mimeType);
state = STATE_PREPARED; state = STATE_PREPARED;
return true; return true;
} }
@ -413,7 +414,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
} }
@Override @Override
public void onLoaded() { public void onLoadCompleted(Loadable loadable) {
Chunk currentLoadable = currentLoadableHolder.chunk; Chunk currentLoadable = currentLoadableHolder.chunk;
notifyLoadCompleted(currentLoadable.bytesLoaded()); notifyLoadCompleted(currentLoadable.bytesLoaded());
try { try {
@ -436,7 +437,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
} }
@Override @Override
public void onCanceled() { public void onLoadCanceled(Loadable loadable) {
Chunk currentLoadable = currentLoadableHolder.chunk; Chunk currentLoadable = currentLoadableHolder.chunk;
notifyLoadCanceled(currentLoadable.bytesLoaded()); notifyLoadCanceled(currentLoadable.bytesLoaded());
if (!isMediaChunk(currentLoadable)) { if (!isMediaChunk(currentLoadable)) {
@ -452,7 +453,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
} }
@Override @Override
public void onError(IOException e) { public void onLoadError(Loadable loadable, IOException e) {
currentLoadableException = e; currentLoadableException = e;
currentLoadableExceptionCount++; currentLoadableExceptionCount++;
currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime(); currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime();
@ -553,7 +554,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
discardUpstreamMediaChunks(currentLoadableHolder.queueSize); discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
if (currentLoadableHolder.chunk == backedOffChunk) { if (currentLoadableHolder.chunk == backedOffChunk) {
// Chunk was unchanged. Resume loading. // Chunk was unchanged. Resume loading.
loader.startLoading(backedOffChunk); loader.startLoading(backedOffChunk, this);
} else { } else {
backedOffChunk.release(); backedOffChunk.release();
maybeStartLoading(); maybeStartLoading();
@ -564,7 +565,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
if (backedOffChunk == mediaChunks.getFirst()) { if (backedOffChunk == mediaChunks.getFirst()) {
// We're not able to clear the first media chunk, so we have no choice but to continue // We're not able to clear the first media chunk, so we have no choice but to continue
// loading it. // loading it.
loader.startLoading(backedOffChunk); loader.startLoading(backedOffChunk, this);
return; return;
} }
@ -579,7 +580,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
if (currentLoadableHolder.chunk == backedOffChunk) { if (currentLoadableHolder.chunk == backedOffChunk) {
// Chunk was unchanged. Resume loading. // Chunk was unchanged. Resume loading.
loader.startLoading(backedOffChunk); loader.startLoading(backedOffChunk, this);
} else { } else {
// This call will remove and release at least one chunk from the end of mediaChunks. Since // This call will remove and release at least one chunk from the end of mediaChunks. Since
// the current loadable is the last media chunk, it is guaranteed to be removed. // the current loadable is the last media chunk, it is guaranteed to be removed.
@ -609,7 +610,7 @@ public class ChunkSampleSource implements SampleSource, Loader.Listener {
notifyLoadStarted(currentLoadable.format.id, currentLoadable.trigger, true, -1, -1, notifyLoadStarted(currentLoadable.format.id, currentLoadable.trigger, true, -1, -1,
currentLoadable.getLength()); currentLoadable.getLength());
} }
loader.startLoading(currentLoadable); loader.startLoading(currentLoadable, this);
} }
/** /**

View File

@ -20,6 +20,7 @@ import com.google.android.exoplayer.util.Util;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.util.Log; import android.util.Log;
@ -72,22 +73,28 @@ public final class Loader {
/** /**
* Interface definition for a callback to be notified of {@link Loader} events. * Interface definition for a callback to be notified of {@link Loader} events.
*/ */
public interface Listener { public interface Callback {
/** /**
* Invoked when loading has been canceled. * Invoked when loading has been canceled.
*
* @param loadable The loadable whose load has been canceled.
*/ */
void onCanceled(); void onLoadCanceled(Loadable loadable);
/** /**
* Invoked when the data source has been fully loaded. * Invoked when the data source has been fully loaded.
*
* @param loadable The loadable whose load has completed.
*/ */
void onLoaded(); void onLoadCompleted(Loadable loadable);
/** /**
* Invoked when the data source is stopped due to an error. * Invoked when the data source is stopped due to an error.
*
* @param loadable The loadable whose load has failed.
*/ */
void onError(IOException exception); void onLoadError(Loadable loadable, IOException exception);
} }
@ -95,18 +102,29 @@ public final class Loader {
private static final int MSG_ERROR = 1; private static final int MSG_ERROR = 1;
private final ExecutorService downloadExecutorService; private final ExecutorService downloadExecutorService;
private final Listener listener;
private LoadTask currentTask; private LoadTask currentTask;
private boolean loading; private boolean loading;
/** /**
* @param threadName A name for the loader's thread. * @param threadName A name for the loader's thread.
* @param listener A listener to invoke when state changes occur.
*/ */
public Loader(String threadName, Listener listener) { public Loader(String threadName) {
this.downloadExecutorService = Util.newSingleThreadExecutor(threadName); this.downloadExecutorService = Util.newSingleThreadExecutor(threadName);
this.listener = listener; }
/**
* Invokes {@link #startLoading(Looper, Loadable, Callback)}, using the {@link Looper}
* associated with the calling thread.
*
* @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);
} }
/** /**
@ -115,12 +133,14 @@ public final class Loader {
* A {@link Loader} instance can only load one {@link Loadable} at a time, and so this method * 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. * must not be called when another load is in progress.
* *
* @param looper The looper of the thread on which the callback should be invoked.
* @param loadable The {@link Loadable} to load. * @param loadable The {@link Loadable} to load.
* @param callback A callback to invoke when the load ends.
*/ */
public void startLoading(Loadable loadable) { public void startLoading(Looper looper, Loadable loadable, Callback callback) {
Assertions.checkState(!loading); Assertions.checkState(!loading);
loading = true; loading = true;
currentTask = new LoadTask(loadable); currentTask = new LoadTask(looper, loadable, callback);
downloadExecutorService.submit(currentTask); downloadExecutorService.submit(currentTask);
} }
@ -161,11 +181,14 @@ public final class Loader {
private static final String TAG = "LoadTask"; private static final String TAG = "LoadTask";
private final Loadable loadable; private final Loadable loadable;
private final Loader.Callback callback;
private volatile Thread executorThread; private volatile Thread executorThread;
public LoadTask(Loadable loadable) { public LoadTask(Looper looper, Loadable loadable, Loader.Callback callback) {
super(looper);
this.loadable = loadable; this.loadable = loadable;
this.callback = callback;
} }
public void quit() { public void quit() {
@ -200,15 +223,15 @@ public final class Loader {
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
onFinished(); onFinished();
if (loadable.isLoadCanceled()) { if (loadable.isLoadCanceled()) {
listener.onCanceled(); callback.onLoadCanceled(loadable);
return; return;
} }
switch (msg.what) { switch (msg.what) {
case MSG_END_OF_SOURCE: case MSG_END_OF_SOURCE:
listener.onLoaded(); callback.onLoadCompleted(loadable);
break; break;
case MSG_ERROR: case MSG_ERROR:
listener.onError((IOException) msg.obj); callback.onLoadError(loadable, (IOException) msg.obj);
break; break;
} }
} }