Improve progress reporting logic
- Listener based reporting of progress allows the content length to be persisted into the download index (and notified via a download state change) as soon as it's available. - Moved contentLength back into Download proper. It should only ever change once, so I'm not sure it belongs in the mutable part of Download. - Made a DownloadProgress class, for naming sanity. PiperOrigin-RevId: 244242487
This commit is contained in:
parent
0ccda60ab4
commit
82061e9afb
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.offline;
|
|||||||
import static com.google.android.exoplayer2.offline.Download.STATE_QUEUED;
|
import static com.google.android.exoplayer2.offline.Download.STATE_QUEUED;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@ -97,10 +98,11 @@ public final class ActionFileUpgradeUtil {
|
|||||||
new Download(
|
new Download(
|
||||||
request,
|
request,
|
||||||
STATE_QUEUED,
|
STATE_QUEUED,
|
||||||
Download.FAILURE_REASON_NONE,
|
|
||||||
Download.STOP_REASON_NONE,
|
|
||||||
/* startTimeMs= */ nowMs,
|
/* startTimeMs= */ nowMs,
|
||||||
/* updateTimeMs= */ nowMs);
|
/* updateTimeMs= */ nowMs,
|
||||||
|
/* contentLength= */ C.LENGTH_UNSET,
|
||||||
|
Download.STOP_REASON_NONE,
|
||||||
|
Download.FAILURE_REASON_NONE);
|
||||||
}
|
}
|
||||||
downloadIndex.putDownload(download);
|
downloadIndex.putDownload(download);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@ import android.text.TextUtils;
|
|||||||
import com.google.android.exoplayer2.database.DatabaseIOException;
|
import com.google.android.exoplayer2.database.DatabaseIOException;
|
||||||
import com.google.android.exoplayer2.database.DatabaseProvider;
|
import com.google.android.exoplayer2.database.DatabaseProvider;
|
||||||
import com.google.android.exoplayer2.database.VersionTable;
|
import com.google.android.exoplayer2.database.VersionTable;
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters;
|
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -210,15 +209,15 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex {
|
|||||||
values.put(COLUMN_CUSTOM_CACHE_KEY, download.request.customCacheKey);
|
values.put(COLUMN_CUSTOM_CACHE_KEY, download.request.customCacheKey);
|
||||||
values.put(COLUMN_DATA, download.request.data);
|
values.put(COLUMN_DATA, download.request.data);
|
||||||
values.put(COLUMN_STATE, download.state);
|
values.put(COLUMN_STATE, download.state);
|
||||||
values.put(COLUMN_DOWNLOAD_PERCENTAGE, download.getDownloadPercentage());
|
|
||||||
values.put(COLUMN_DOWNLOADED_BYTES, download.getDownloadedBytes());
|
|
||||||
values.put(COLUMN_TOTAL_BYTES, download.getTotalBytes());
|
|
||||||
values.put(COLUMN_FAILURE_REASON, download.failureReason);
|
|
||||||
values.put(COLUMN_STOP_FLAGS, 0);
|
|
||||||
values.put(COLUMN_NOT_MET_REQUIREMENTS, 0);
|
|
||||||
values.put(COLUMN_STOP_REASON, download.stopReason);
|
|
||||||
values.put(COLUMN_START_TIME_MS, download.startTimeMs);
|
values.put(COLUMN_START_TIME_MS, download.startTimeMs);
|
||||||
values.put(COLUMN_UPDATE_TIME_MS, download.updateTimeMs);
|
values.put(COLUMN_UPDATE_TIME_MS, download.updateTimeMs);
|
||||||
|
values.put(COLUMN_TOTAL_BYTES, download.contentLength);
|
||||||
|
values.put(COLUMN_STOP_REASON, download.stopReason);
|
||||||
|
values.put(COLUMN_FAILURE_REASON, download.failureReason);
|
||||||
|
values.put(COLUMN_DOWNLOAD_PERCENTAGE, download.getPercentDownloaded());
|
||||||
|
values.put(COLUMN_DOWNLOADED_BYTES, download.getBytesDownloaded());
|
||||||
|
values.put(COLUMN_STOP_FLAGS, 0);
|
||||||
|
values.put(COLUMN_NOT_MET_REQUIREMENTS, 0);
|
||||||
try {
|
try {
|
||||||
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
|
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
|
||||||
writableDatabase.replaceOrThrow(tableName, /* nullColumnHack= */ null, values);
|
writableDatabase.replaceOrThrow(tableName, /* nullColumnHack= */ null, values);
|
||||||
@ -337,18 +336,18 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex {
|
|||||||
decodeStreamKeys(cursor.getString(COLUMN_INDEX_STREAM_KEYS)),
|
decodeStreamKeys(cursor.getString(COLUMN_INDEX_STREAM_KEYS)),
|
||||||
cursor.getString(COLUMN_INDEX_CUSTOM_CACHE_KEY),
|
cursor.getString(COLUMN_INDEX_CUSTOM_CACHE_KEY),
|
||||||
cursor.getBlob(COLUMN_INDEX_DATA));
|
cursor.getBlob(COLUMN_INDEX_DATA));
|
||||||
CachingCounters cachingCounters = new CachingCounters();
|
DownloadProgress downloadProgress = new DownloadProgress();
|
||||||
cachingCounters.alreadyCachedBytes = cursor.getLong(COLUMN_INDEX_DOWNLOADED_BYTES);
|
downloadProgress.bytesDownloaded = cursor.getLong(COLUMN_INDEX_DOWNLOADED_BYTES);
|
||||||
cachingCounters.contentLength = cursor.getLong(COLUMN_INDEX_TOTAL_BYTES);
|
downloadProgress.percentDownloaded = cursor.getFloat(COLUMN_INDEX_DOWNLOAD_PERCENTAGE);
|
||||||
cachingCounters.percentage = cursor.getFloat(COLUMN_INDEX_DOWNLOAD_PERCENTAGE);
|
|
||||||
return new Download(
|
return new Download(
|
||||||
request,
|
request,
|
||||||
cursor.getInt(COLUMN_INDEX_STATE),
|
cursor.getInt(COLUMN_INDEX_STATE),
|
||||||
cursor.getInt(COLUMN_INDEX_FAILURE_REASON),
|
|
||||||
cursor.getInt(COLUMN_INDEX_STOP_REASON),
|
|
||||||
cursor.getLong(COLUMN_INDEX_START_TIME_MS),
|
cursor.getLong(COLUMN_INDEX_START_TIME_MS),
|
||||||
cursor.getLong(COLUMN_INDEX_UPDATE_TIME_MS),
|
cursor.getLong(COLUMN_INDEX_UPDATE_TIME_MS),
|
||||||
cachingCounters);
|
cursor.getLong(COLUMN_INDEX_TOTAL_BYTES),
|
||||||
|
cursor.getInt(COLUMN_INDEX_STOP_REASON),
|
||||||
|
cursor.getInt(COLUMN_INDEX_FAILURE_REASON),
|
||||||
|
downloadProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String encodeStreamKeys(List<StreamKey> streamKeys) {
|
private static String encodeStreamKeys(List<StreamKey> streamKeys) {
|
||||||
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.offline;
|
|||||||
|
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters;
|
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@ -96,60 +95,65 @@ public final class Download {
|
|||||||
|
|
||||||
/** The download request. */
|
/** The download request. */
|
||||||
public final DownloadRequest request;
|
public final DownloadRequest request;
|
||||||
|
|
||||||
/** The state of the download. */
|
/** The state of the download. */
|
||||||
@State public final int state;
|
@State public final int state;
|
||||||
/** The first time when download entry is created. */
|
/** The first time when download entry is created. */
|
||||||
public final long startTimeMs;
|
public final long startTimeMs;
|
||||||
/** The last update time. */
|
/** The last update time. */
|
||||||
public final long updateTimeMs;
|
public final long updateTimeMs;
|
||||||
|
/** The total size of the content in bytes, or {@link C#LENGTH_UNSET} if unknown. */
|
||||||
|
public final long contentLength;
|
||||||
|
/** The reason the download is stopped, or {@link #STOP_REASON_NONE}. */
|
||||||
|
public final int stopReason;
|
||||||
/**
|
/**
|
||||||
* If {@link #state} is {@link #STATE_FAILED} then this is the cause, otherwise {@link
|
* If {@link #state} is {@link #STATE_FAILED} then this is the cause, otherwise {@link
|
||||||
* #FAILURE_REASON_NONE}.
|
* #FAILURE_REASON_NONE}.
|
||||||
*/
|
*/
|
||||||
@FailureReason public final int failureReason;
|
@FailureReason public final int failureReason;
|
||||||
/** The reason the download is stopped, or {@link #STOP_REASON_NONE}. */
|
|
||||||
public final int stopReason;
|
|
||||||
|
|
||||||
/* package */ CachingCounters counters;
|
/* package */ final DownloadProgress progress;
|
||||||
|
|
||||||
/* package */ Download(
|
public Download(
|
||||||
DownloadRequest request,
|
DownloadRequest request,
|
||||||
@State int state,
|
@State int state,
|
||||||
@FailureReason int failureReason,
|
|
||||||
int stopReason,
|
|
||||||
long startTimeMs,
|
long startTimeMs,
|
||||||
long updateTimeMs) {
|
long updateTimeMs,
|
||||||
|
long contentLength,
|
||||||
|
int stopReason,
|
||||||
|
@FailureReason int failureReason) {
|
||||||
this(
|
this(
|
||||||
request,
|
request,
|
||||||
state,
|
state,
|
||||||
failureReason,
|
|
||||||
stopReason,
|
|
||||||
startTimeMs,
|
startTimeMs,
|
||||||
updateTimeMs,
|
updateTimeMs,
|
||||||
new CachingCounters());
|
contentLength,
|
||||||
|
stopReason,
|
||||||
|
failureReason,
|
||||||
|
new DownloadProgress());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ Download(
|
public Download(
|
||||||
DownloadRequest request,
|
DownloadRequest request,
|
||||||
@State int state,
|
@State int state,
|
||||||
@FailureReason int failureReason,
|
|
||||||
int stopReason,
|
|
||||||
long startTimeMs,
|
long startTimeMs,
|
||||||
long updateTimeMs,
|
long updateTimeMs,
|
||||||
CachingCounters counters) {
|
long contentLength,
|
||||||
Assertions.checkNotNull(counters);
|
int stopReason,
|
||||||
|
@FailureReason int failureReason,
|
||||||
|
DownloadProgress progress) {
|
||||||
|
Assertions.checkNotNull(progress);
|
||||||
Assertions.checkState((failureReason == FAILURE_REASON_NONE) == (state != STATE_FAILED));
|
Assertions.checkState((failureReason == FAILURE_REASON_NONE) == (state != STATE_FAILED));
|
||||||
if (stopReason != 0) {
|
if (stopReason != 0) {
|
||||||
Assertions.checkState(state != STATE_DOWNLOADING && state != STATE_QUEUED);
|
Assertions.checkState(state != STATE_DOWNLOADING && state != STATE_QUEUED);
|
||||||
}
|
}
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.failureReason = failureReason;
|
|
||||||
this.stopReason = stopReason;
|
|
||||||
this.startTimeMs = startTimeMs;
|
this.startTimeMs = startTimeMs;
|
||||||
this.updateTimeMs = updateTimeMs;
|
this.updateTimeMs = updateTimeMs;
|
||||||
this.counters = counters;
|
this.contentLength = contentLength;
|
||||||
|
this.stopReason = stopReason;
|
||||||
|
this.failureReason = failureReason;
|
||||||
|
this.progress = progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns whether the download is completed or failed. These are terminal states. */
|
/** Returns whether the download is completed or failed. These are terminal states. */
|
||||||
@ -158,30 +162,15 @@ public final class Download {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the total number of downloaded bytes. */
|
/** Returns the total number of downloaded bytes. */
|
||||||
public long getDownloadedBytes() {
|
public long getBytesDownloaded() {
|
||||||
return counters.totalCachedBytes();
|
return progress.bytesDownloaded;
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the total size of the media, or {@link C#LENGTH_UNSET} if unknown. */
|
|
||||||
public long getTotalBytes() {
|
|
||||||
return counters.contentLength;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the estimated download percentage, or {@link C#PERCENTAGE_UNSET} if no estimate is
|
* Returns the estimated download percentage, or {@link C#PERCENTAGE_UNSET} if no estimate is
|
||||||
* available.
|
* available.
|
||||||
*/
|
*/
|
||||||
public float getDownloadPercentage() {
|
public float getPercentDownloaded() {
|
||||||
return counters.percentage;
|
return progress.percentDownloaded;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets counters which are updated by a {@link Downloader}.
|
|
||||||
*
|
|
||||||
* @param counters An instance of {@link CachingCounters}.
|
|
||||||
*/
|
|
||||||
protected void setCounters(CachingCounters counters) {
|
|
||||||
Assertions.checkNotNull(counters);
|
|
||||||
this.counters = counters;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,6 @@ import androidx.annotation.Nullable;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.scheduler.Requirements;
|
import com.google.android.exoplayer2.scheduler.Requirements;
|
||||||
import com.google.android.exoplayer2.scheduler.RequirementsWatcher;
|
import com.google.android.exoplayer2.scheduler.RequirementsWatcher;
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters;
|
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
@ -131,7 +130,8 @@ public final class DownloadManager {
|
|||||||
private static final int MSG_ADD_DOWNLOAD = 4;
|
private static final int MSG_ADD_DOWNLOAD = 4;
|
||||||
private static final int MSG_REMOVE_DOWNLOAD = 5;
|
private static final int MSG_REMOVE_DOWNLOAD = 5;
|
||||||
private static final int MSG_DOWNLOAD_THREAD_STOPPED = 6;
|
private static final int MSG_DOWNLOAD_THREAD_STOPPED = 6;
|
||||||
private static final int MSG_RELEASE = 7;
|
private static final int MSG_CONTENT_LENGTH_CHANGED = 7;
|
||||||
|
private static final int MSG_RELEASE = 8;
|
||||||
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@IntDef({
|
@IntDef({
|
||||||
@ -539,6 +539,11 @@ public final class DownloadManager {
|
|||||||
onDownloadThreadStoppedInternal(downloadThread);
|
onDownloadThreadStoppedInternal(downloadThread);
|
||||||
processedExternalMessage = false; // This message is posted internally.
|
processedExternalMessage = false; // This message is posted internally.
|
||||||
break;
|
break;
|
||||||
|
case MSG_CONTENT_LENGTH_CHANGED:
|
||||||
|
downloadThread = (DownloadThread) message.obj;
|
||||||
|
onDownloadThreadContentLengthChangedInternal(downloadThread);
|
||||||
|
processedExternalMessage = false; // This message is posted internally.
|
||||||
|
break;
|
||||||
case MSG_RELEASE:
|
case MSG_RELEASE:
|
||||||
releaseInternal();
|
releaseInternal();
|
||||||
return true; // Don't post back to mainHandler on release.
|
return true; // Don't post back to mainHandler on release.
|
||||||
@ -634,10 +639,11 @@ public final class DownloadManager {
|
|||||||
new Download(
|
new Download(
|
||||||
request,
|
request,
|
||||||
stopReason != Download.STOP_REASON_NONE ? STATE_STOPPED : STATE_QUEUED,
|
stopReason != Download.STOP_REASON_NONE ? STATE_STOPPED : STATE_QUEUED,
|
||||||
Download.FAILURE_REASON_NONE,
|
|
||||||
stopReason,
|
|
||||||
/* startTimeMs= */ nowMs,
|
/* startTimeMs= */ nowMs,
|
||||||
/* updateTimeMs= */ nowMs);
|
/* updateTimeMs= */ nowMs,
|
||||||
|
/* contentLength= */ C.LENGTH_UNSET,
|
||||||
|
stopReason,
|
||||||
|
Download.FAILURE_REASON_NONE);
|
||||||
logd("Download state is created for " + request.id);
|
logd("Download state is created for " + request.id);
|
||||||
} else {
|
} else {
|
||||||
download = mergeRequest(download, request, stopReason);
|
download = mergeRequest(download, request, stopReason);
|
||||||
@ -682,6 +688,11 @@ public final class DownloadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onDownloadThreadContentLengthChangedInternal(DownloadThread downloadThread) {
|
||||||
|
String downloadId = downloadThread.request.id;
|
||||||
|
getDownload(downloadId).setContentLength(downloadThread.contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
private void releaseInternal() {
|
private void releaseInternal() {
|
||||||
for (DownloadThread downloadThread : downloadThreads.values()) {
|
for (DownloadThread downloadThread : downloadThreads.values()) {
|
||||||
downloadThread.cancel(/* released= */ true);
|
downloadThread.cancel(/* released= */ true);
|
||||||
@ -737,10 +748,11 @@ public final class DownloadManager {
|
|||||||
parallelDownloads++;
|
parallelDownloads++;
|
||||||
}
|
}
|
||||||
Downloader downloader = downloaderFactory.createDownloader(request);
|
Downloader downloader = downloaderFactory.createDownloader(request);
|
||||||
|
DownloadProgress downloadProgress = downloadInternal.download.progress;
|
||||||
DownloadThread downloadThread =
|
DownloadThread downloadThread =
|
||||||
new DownloadThread(request, downloader, isRemove, minRetryCount, internalHandler);
|
new DownloadThread(
|
||||||
|
request, downloader, downloadProgress, isRemove, minRetryCount, internalHandler);
|
||||||
downloadThreads.put(downloadId, downloadThread);
|
downloadThreads.put(downloadId, downloadThread);
|
||||||
downloadInternal.setCounters(downloadThread.downloader.getCounters());
|
|
||||||
downloadThread.start();
|
downloadThread.start();
|
||||||
logd("Download is started", downloadInternal);
|
logd("Download is started", downloadInternal);
|
||||||
return START_THREAD_SUCCEEDED;
|
return START_THREAD_SUCCEEDED;
|
||||||
@ -802,22 +814,23 @@ public final class DownloadManager {
|
|||||||
return new Download(
|
return new Download(
|
||||||
download.request.copyWithMergedRequest(request),
|
download.request.copyWithMergedRequest(request),
|
||||||
state,
|
state,
|
||||||
FAILURE_REASON_NONE,
|
|
||||||
stopReason,
|
|
||||||
startTimeMs,
|
startTimeMs,
|
||||||
/* updateTimeMs= */ nowMs,
|
/* updateTimeMs= */ nowMs,
|
||||||
download.counters);
|
/* contentLength= */ C.LENGTH_UNSET,
|
||||||
|
stopReason,
|
||||||
|
FAILURE_REASON_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Download copyWithState(Download download, @Download.State int state) {
|
private static Download copyWithState(Download download, @Download.State int state) {
|
||||||
return new Download(
|
return new Download(
|
||||||
download.request,
|
download.request,
|
||||||
state,
|
state,
|
||||||
FAILURE_REASON_NONE,
|
|
||||||
download.stopReason,
|
|
||||||
download.startTimeMs,
|
download.startTimeMs,
|
||||||
/* updateTimeMs= */ System.currentTimeMillis(),
|
/* updateTimeMs= */ System.currentTimeMillis(),
|
||||||
download.counters);
|
download.contentLength,
|
||||||
|
download.stopReason,
|
||||||
|
FAILURE_REASON_NONE,
|
||||||
|
download.progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void logd(String message) {
|
private static void logd(String message) {
|
||||||
@ -850,13 +863,17 @@ public final class DownloadManager {
|
|||||||
|
|
||||||
// TODO: Get rid of these and use download directly.
|
// TODO: Get rid of these and use download directly.
|
||||||
@Download.State private int state;
|
@Download.State private int state;
|
||||||
|
private long contentLength;
|
||||||
private int stopReason;
|
private int stopReason;
|
||||||
@MonotonicNonNull @Download.FailureReason private int failureReason;
|
@MonotonicNonNull @Download.FailureReason private int failureReason;
|
||||||
|
|
||||||
private DownloadInternal(DownloadManager downloadManager, Download download) {
|
private DownloadInternal(DownloadManager downloadManager, Download download) {
|
||||||
this.downloadManager = downloadManager;
|
this.downloadManager = downloadManager;
|
||||||
this.download = download;
|
this.download = download;
|
||||||
|
state = download.state;
|
||||||
|
contentLength = download.contentLength;
|
||||||
stopReason = download.stopReason;
|
stopReason = download.stopReason;
|
||||||
|
failureReason = download.failureReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
@ -877,11 +894,12 @@ public final class DownloadManager {
|
|||||||
new Download(
|
new Download(
|
||||||
download.request,
|
download.request,
|
||||||
state,
|
state,
|
||||||
state != STATE_FAILED ? FAILURE_REASON_NONE : failureReason,
|
|
||||||
stopReason,
|
|
||||||
download.startTimeMs,
|
download.startTimeMs,
|
||||||
/* updateTimeMs= */ System.currentTimeMillis(),
|
/* updateTimeMs= */ System.currentTimeMillis(),
|
||||||
download.counters);
|
contentLength,
|
||||||
|
stopReason,
|
||||||
|
state != STATE_FAILED ? FAILURE_REASON_NONE : failureReason,
|
||||||
|
download.progress);
|
||||||
return download;
|
return download;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -911,8 +929,12 @@ public final class DownloadManager {
|
|||||||
return state == STATE_REMOVING || state == STATE_RESTARTING;
|
return state == STATE_REMOVING || state == STATE_RESTARTING;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCounters(CachingCounters counters) {
|
public void setContentLength(long contentLength) {
|
||||||
download.setCounters(counters);
|
if (this.contentLength == contentLength) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.contentLength = contentLength;
|
||||||
|
downloadManager.onDownloadChangedInternal(this, getUpdatedDownload());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStopState() {
|
private void updateStopState() {
|
||||||
@ -992,28 +1014,34 @@ public final class DownloadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DownloadThread extends Thread {
|
private static class DownloadThread extends Thread implements Downloader.ProgressListener {
|
||||||
|
|
||||||
private final DownloadRequest request;
|
private final DownloadRequest request;
|
||||||
private final Downloader downloader;
|
private final Downloader downloader;
|
||||||
|
private final DownloadProgress downloadProgress;
|
||||||
private final boolean isRemove;
|
private final boolean isRemove;
|
||||||
private final int minRetryCount;
|
private final int minRetryCount;
|
||||||
|
|
||||||
private volatile Handler onStoppedHandler;
|
private volatile Handler updateHandler;
|
||||||
private volatile boolean isCanceled;
|
private volatile boolean isCanceled;
|
||||||
private Throwable finalError;
|
private Throwable finalError;
|
||||||
|
|
||||||
|
private long contentLength;
|
||||||
|
|
||||||
private DownloadThread(
|
private DownloadThread(
|
||||||
DownloadRequest request,
|
DownloadRequest request,
|
||||||
Downloader downloader,
|
Downloader downloader,
|
||||||
|
DownloadProgress downloadProgress,
|
||||||
boolean isRemove,
|
boolean isRemove,
|
||||||
int minRetryCount,
|
int minRetryCount,
|
||||||
Handler onStoppedHandler) {
|
Handler updateHandler) {
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.isRemove = isRemove;
|
|
||||||
this.downloader = downloader;
|
this.downloader = downloader;
|
||||||
|
this.downloadProgress = downloadProgress;
|
||||||
|
this.isRemove = isRemove;
|
||||||
this.minRetryCount = minRetryCount;
|
this.minRetryCount = minRetryCount;
|
||||||
this.onStoppedHandler = onStoppedHandler;
|
this.updateHandler = updateHandler;
|
||||||
|
contentLength = C.LENGTH_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel(boolean released) {
|
public void cancel(boolean released) {
|
||||||
@ -1022,7 +1050,7 @@ public final class DownloadManager {
|
|||||||
// cancellation to complete depends on the implementation of the downloader being used. We
|
// cancellation to complete depends on the implementation of the downloader being used. We
|
||||||
// null the handler reference here so that it doesn't prevent garbage collection of the
|
// null the handler reference here so that it doesn't prevent garbage collection of the
|
||||||
// download manager whilst cancellation is ongoing.
|
// download manager whilst cancellation is ongoing.
|
||||||
onStoppedHandler = null;
|
updateHandler = null;
|
||||||
}
|
}
|
||||||
isCanceled = true;
|
isCanceled = true;
|
||||||
downloader.cancel();
|
downloader.cancel();
|
||||||
@ -1042,14 +1070,14 @@ public final class DownloadManager {
|
|||||||
long errorPosition = C.LENGTH_UNSET;
|
long errorPosition = C.LENGTH_UNSET;
|
||||||
while (!isCanceled) {
|
while (!isCanceled) {
|
||||||
try {
|
try {
|
||||||
downloader.download();
|
downloader.download(/* progressListener= */ this);
|
||||||
break;
|
break;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (!isCanceled) {
|
if (!isCanceled) {
|
||||||
long downloadedBytes = downloader.getDownloadedBytes();
|
long bytesDownloaded = downloadProgress.bytesDownloaded;
|
||||||
if (downloadedBytes != errorPosition) {
|
if (bytesDownloaded != errorPosition) {
|
||||||
logd("Reset error count. downloadedBytes = " + downloadedBytes, request);
|
logd("Reset error count. bytesDownloaded = " + bytesDownloaded, request);
|
||||||
errorPosition = downloadedBytes;
|
errorPosition = bytesDownloaded;
|
||||||
errorCount = 0;
|
errorCount = 0;
|
||||||
}
|
}
|
||||||
if (++errorCount > minRetryCount) {
|
if (++errorCount > minRetryCount) {
|
||||||
@ -1064,13 +1092,26 @@ public final class DownloadManager {
|
|||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
finalError = e;
|
finalError = e;
|
||||||
}
|
}
|
||||||
Handler onStoppedHandler = this.onStoppedHandler;
|
Handler updateHandler = this.updateHandler;
|
||||||
if (onStoppedHandler != null) {
|
if (updateHandler != null) {
|
||||||
onStoppedHandler.obtainMessage(MSG_DOWNLOAD_THREAD_STOPPED, this).sendToTarget();
|
updateHandler.obtainMessage(MSG_DOWNLOAD_THREAD_STOPPED, this).sendToTarget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getRetryDelayMillis(int errorCount) {
|
@Override
|
||||||
|
public void onProgress(long contentLength, long bytesDownloaded, float percentDownloaded) {
|
||||||
|
downloadProgress.bytesDownloaded = bytesDownloaded;
|
||||||
|
downloadProgress.percentDownloaded = percentDownloaded;
|
||||||
|
if (contentLength != this.contentLength) {
|
||||||
|
this.contentLength = contentLength;
|
||||||
|
Handler updateHandler = this.updateHandler;
|
||||||
|
if (updateHandler != null) {
|
||||||
|
updateHandler.obtainMessage(MSG_CONTENT_LENGTH_CHANGED, this).sendToTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getRetryDelayMillis(int errorCount) {
|
||||||
return Math.min((errorCount - 1) * 1000, 5000);
|
return Math.min((errorCount - 1) * 1000, 5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer2.offline;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
|
||||||
|
/** Mutable {@link Download} progress. */
|
||||||
|
public class DownloadProgress {
|
||||||
|
|
||||||
|
/** The number of bytes that have been downloaded. */
|
||||||
|
public long bytesDownloaded;
|
||||||
|
|
||||||
|
/** The percentage that has been downloaded, or {@link C#PERCENTAGE_UNSET} if unknown. */
|
||||||
|
public float percentDownloaded;
|
||||||
|
}
|
@ -15,44 +15,44 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.offline;
|
package com.google.android.exoplayer2.offline;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/** Downloads and removes a piece of content. */
|
||||||
* An interface for stream downloaders.
|
|
||||||
*/
|
|
||||||
public interface Downloader {
|
public interface Downloader {
|
||||||
|
|
||||||
|
/** Receives progress updates during download operations. */
|
||||||
|
interface ProgressListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads the media.
|
* Called when progress is made during a download operation.
|
||||||
*
|
*
|
||||||
* @throws DownloadException Thrown if the media cannot be downloaded.
|
* @param contentLength The length of the content in bytes, or {@link C#LENGTH_UNSET} if
|
||||||
|
* unknown.
|
||||||
|
* @param bytesDownloaded The number of bytes that have been downloaded.
|
||||||
|
* @param percentDownloaded The percentage of the content that has been downloaded, or {@link
|
||||||
|
* C#PERCENTAGE_UNSET}.
|
||||||
|
*/
|
||||||
|
void onProgress(long contentLength, long bytesDownloaded, float percentDownloaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads the content.
|
||||||
|
*
|
||||||
|
* @param progressListener A listener to receive progress updates, or {@code null}.
|
||||||
|
* @throws DownloadException Thrown if the content cannot be downloaded.
|
||||||
* @throws InterruptedException If the thread has been interrupted.
|
* @throws InterruptedException If the thread has been interrupted.
|
||||||
* @throws IOException Thrown when there is an io error while downloading.
|
* @throws IOException Thrown when there is an io error while downloading.
|
||||||
*/
|
*/
|
||||||
void download() throws InterruptedException, IOException;
|
void download(@Nullable ProgressListener progressListener)
|
||||||
|
throws InterruptedException, IOException;
|
||||||
|
|
||||||
/** Interrupts any current download operation and prevents future operations from running. */
|
/** Cancels the download operation and prevents future download operations from running. */
|
||||||
void cancel();
|
void cancel();
|
||||||
|
|
||||||
/** Returns the total number of downloaded bytes. */
|
|
||||||
long getDownloadedBytes();
|
|
||||||
|
|
||||||
/** Returns the total size of the media, or {@link C#LENGTH_UNSET} if unknown. */
|
|
||||||
long getTotalBytes();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the estimated download percentage, or {@link C#PERCENTAGE_UNSET} if no estimate is
|
* Removes the content.
|
||||||
* available.
|
|
||||||
*/
|
|
||||||
float getDownloadPercentage();
|
|
||||||
|
|
||||||
/** Returns a {@link CachingCounters} which holds download counters. */
|
|
||||||
CachingCounters getCounters();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the media.
|
|
||||||
*
|
*
|
||||||
* @throws InterruptedException Thrown if the thread was interrupted.
|
* @throws InterruptedException Thrown if the thread was interrupted.
|
||||||
*/
|
*/
|
||||||
|
@ -23,7 +23,6 @@ import com.google.android.exoplayer2.upstream.cache.Cache;
|
|||||||
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
|
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheKeyFactory;
|
import com.google.android.exoplayer2.upstream.cache.CacheKeyFactory;
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheUtil;
|
import com.google.android.exoplayer2.upstream.cache.CacheUtil;
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters;
|
|
||||||
import com.google.android.exoplayer2.util.PriorityTaskManager;
|
import com.google.android.exoplayer2.util.PriorityTaskManager;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
@ -40,7 +39,6 @@ public final class ProgressiveDownloader implements Downloader {
|
|||||||
private final CacheDataSource dataSource;
|
private final CacheDataSource dataSource;
|
||||||
private final CacheKeyFactory cacheKeyFactory;
|
private final CacheKeyFactory cacheKeyFactory;
|
||||||
private final PriorityTaskManager priorityTaskManager;
|
private final PriorityTaskManager priorityTaskManager;
|
||||||
private final CacheUtil.CachingCounters cachingCounters;
|
|
||||||
private final AtomicBoolean isCanceled;
|
private final AtomicBoolean isCanceled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,12 +60,12 @@ public final class ProgressiveDownloader implements Downloader {
|
|||||||
this.dataSource = constructorHelper.createCacheDataSource();
|
this.dataSource = constructorHelper.createCacheDataSource();
|
||||||
this.cacheKeyFactory = constructorHelper.getCacheKeyFactory();
|
this.cacheKeyFactory = constructorHelper.getCacheKeyFactory();
|
||||||
this.priorityTaskManager = constructorHelper.getPriorityTaskManager();
|
this.priorityTaskManager = constructorHelper.getPriorityTaskManager();
|
||||||
cachingCounters = new CachingCounters();
|
|
||||||
isCanceled = new AtomicBoolean();
|
isCanceled = new AtomicBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void download() throws InterruptedException, IOException {
|
public void download(@Nullable ProgressListener progressListener)
|
||||||
|
throws InterruptedException, IOException {
|
||||||
priorityTaskManager.add(C.PRIORITY_DOWNLOAD);
|
priorityTaskManager.add(C.PRIORITY_DOWNLOAD);
|
||||||
try {
|
try {
|
||||||
CacheUtil.cache(
|
CacheUtil.cache(
|
||||||
@ -78,7 +76,7 @@ public final class ProgressiveDownloader implements Downloader {
|
|||||||
new byte[BUFFER_SIZE_BYTES],
|
new byte[BUFFER_SIZE_BYTES],
|
||||||
priorityTaskManager,
|
priorityTaskManager,
|
||||||
C.PRIORITY_DOWNLOAD,
|
C.PRIORITY_DOWNLOAD,
|
||||||
cachingCounters,
|
progressListener == null ? null : new ProgressForwarder(progressListener),
|
||||||
isCanceled,
|
isCanceled,
|
||||||
/* enableEOFException= */ true);
|
/* enableEOFException= */ true);
|
||||||
} finally {
|
} finally {
|
||||||
@ -91,28 +89,26 @@ public final class ProgressiveDownloader implements Downloader {
|
|||||||
isCanceled.set(true);
|
isCanceled.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getDownloadedBytes() {
|
|
||||||
return cachingCounters.totalCachedBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getTotalBytes() {
|
|
||||||
return cachingCounters.contentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getDownloadPercentage() {
|
|
||||||
return cachingCounters.percentage;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CachingCounters getCounters() {
|
|
||||||
return cachingCounters;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void remove() {
|
public void remove() {
|
||||||
CacheUtil.remove(dataSpec, cache, cacheKeyFactory);
|
CacheUtil.remove(dataSpec, cache, cacheKeyFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class ProgressForwarder implements CacheUtil.ProgressListener {
|
||||||
|
|
||||||
|
private final ProgressListener progessListener;
|
||||||
|
|
||||||
|
public ProgressForwarder(ProgressListener progressListener) {
|
||||||
|
this.progessListener = progressListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgress(long contentLength, long bytesCached, long newBytesCached) {
|
||||||
|
float percentDownloaded =
|
||||||
|
contentLength == C.LENGTH_UNSET || contentLength == 0
|
||||||
|
? C.PERCENTAGE_UNSET
|
||||||
|
: ((bytesCached * 100f) / contentLength);
|
||||||
|
progessListener.onProgress(contentLength, bytesCached, percentDownloaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ package com.google.android.exoplayer2.offline;
|
|||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||||
@ -24,7 +26,6 @@ import com.google.android.exoplayer2.upstream.cache.Cache;
|
|||||||
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
|
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheKeyFactory;
|
import com.google.android.exoplayer2.upstream.cache.CacheKeyFactory;
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheUtil;
|
import com.google.android.exoplayer2.upstream.cache.CacheUtil;
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters;
|
|
||||||
import com.google.android.exoplayer2.util.PriorityTaskManager;
|
import com.google.android.exoplayer2.util.PriorityTaskManager;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -42,6 +43,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
|
|||||||
|
|
||||||
/** Smallest unit of content to be downloaded. */
|
/** Smallest unit of content to be downloaded. */
|
||||||
protected static class Segment implements Comparable<Segment> {
|
protected static class Segment implements Comparable<Segment> {
|
||||||
|
|
||||||
/** The start time of the segment in microseconds. */
|
/** The start time of the segment in microseconds. */
|
||||||
public final long startTimeUs;
|
public final long startTimeUs;
|
||||||
|
|
||||||
@ -70,10 +72,6 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
|
|||||||
private final PriorityTaskManager priorityTaskManager;
|
private final PriorityTaskManager priorityTaskManager;
|
||||||
private final ArrayList<StreamKey> streamKeys;
|
private final ArrayList<StreamKey> streamKeys;
|
||||||
private final AtomicBoolean isCanceled;
|
private final AtomicBoolean isCanceled;
|
||||||
private final CacheUtil.CachingCounters counters;
|
|
||||||
|
|
||||||
private volatile int totalSegments;
|
|
||||||
private volatile int downloadedSegments;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param manifestUri The {@link Uri} of the manifest to be downloaded.
|
* @param manifestUri The {@link Uri} of the manifest to be downloaded.
|
||||||
@ -90,9 +88,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
|
|||||||
this.offlineDataSource = constructorHelper.createOfflineCacheDataSource();
|
this.offlineDataSource = constructorHelper.createOfflineCacheDataSource();
|
||||||
this.cacheKeyFactory = constructorHelper.getCacheKeyFactory();
|
this.cacheKeyFactory = constructorHelper.getCacheKeyFactory();
|
||||||
this.priorityTaskManager = constructorHelper.getPriorityTaskManager();
|
this.priorityTaskManager = constructorHelper.getPriorityTaskManager();
|
||||||
totalSegments = C.LENGTH_UNSET;
|
|
||||||
isCanceled = new AtomicBoolean();
|
isCanceled = new AtomicBoolean();
|
||||||
counters = new CachingCounters();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,20 +98,58 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
|
|||||||
* @throws IOException Thrown when there is an error downloading.
|
* @throws IOException Thrown when there is an error downloading.
|
||||||
* @throws InterruptedException If the thread has been interrupted.
|
* @throws InterruptedException If the thread has been interrupted.
|
||||||
*/
|
*/
|
||||||
// downloadedSegments and downloadedBytes are only written from this method, and this method
|
|
||||||
// should not be called from more than one thread. Hence non-atomic updates are valid.
|
|
||||||
@SuppressWarnings("NonAtomicVolatileUpdate")
|
|
||||||
@Override
|
@Override
|
||||||
public final void download() throws IOException, InterruptedException {
|
public final void download(@Nullable ProgressListener progressListener)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
priorityTaskManager.add(C.PRIORITY_DOWNLOAD);
|
priorityTaskManager.add(C.PRIORITY_DOWNLOAD);
|
||||||
|
try {
|
||||||
|
// Get the manifest and all of the segments.
|
||||||
|
M manifest = getManifest(dataSource, manifestDataSpec);
|
||||||
|
if (!streamKeys.isEmpty()) {
|
||||||
|
manifest = manifest.copy(streamKeys);
|
||||||
|
}
|
||||||
|
List<Segment> segments = getSegments(dataSource, manifest, /* allowIncompleteList= */ false);
|
||||||
|
|
||||||
try {
|
// Scan the segments, removing any that are fully downloaded.
|
||||||
List<Segment> segments = initDownload();
|
int totalSegments = segments.size();
|
||||||
|
int segmentsDownloaded = 0;
|
||||||
|
long contentLength = 0;
|
||||||
|
long bytesDownloaded = 0;
|
||||||
|
for (int i = segments.size() - 1; i >= 0; i--) {
|
||||||
|
Segment segment = segments.get(i);
|
||||||
|
Pair<Long, Long> segmentLengthAndBytesDownloaded =
|
||||||
|
CacheUtil.getCached(segment.dataSpec, cache, cacheKeyFactory);
|
||||||
|
long segmentLength = segmentLengthAndBytesDownloaded.first;
|
||||||
|
long segmentBytesDownloaded = segmentLengthAndBytesDownloaded.second;
|
||||||
|
bytesDownloaded += segmentBytesDownloaded;
|
||||||
|
if (segmentLength != C.LENGTH_UNSET) {
|
||||||
|
if (segmentLength == segmentBytesDownloaded) {
|
||||||
|
// The segment is fully downloaded.
|
||||||
|
segmentsDownloaded++;
|
||||||
|
segments.remove(i);
|
||||||
|
}
|
||||||
|
if (contentLength != C.LENGTH_UNSET) {
|
||||||
|
contentLength += segmentLength;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
contentLength = C.LENGTH_UNSET;
|
||||||
|
}
|
||||||
|
}
|
||||||
Collections.sort(segments);
|
Collections.sort(segments);
|
||||||
|
|
||||||
|
// Download the segments.
|
||||||
|
ProgressNotifier progressNotifier = null;
|
||||||
|
if (progressListener != null) {
|
||||||
|
progressNotifier =
|
||||||
|
new ProgressNotifier(
|
||||||
|
progressListener,
|
||||||
|
contentLength,
|
||||||
|
totalSegments,
|
||||||
|
bytesDownloaded,
|
||||||
|
segmentsDownloaded);
|
||||||
|
}
|
||||||
byte[] buffer = new byte[BUFFER_SIZE_BYTES];
|
byte[] buffer = new byte[BUFFER_SIZE_BYTES];
|
||||||
CachingCounters cachingCounters = new CachingCounters();
|
|
||||||
for (int i = 0; i < segments.size(); i++) {
|
for (int i = 0; i < segments.size(); i++) {
|
||||||
try {
|
|
||||||
CacheUtil.cache(
|
CacheUtil.cache(
|
||||||
segments.get(i).dataSpec,
|
segments.get(i).dataSpec,
|
||||||
cache,
|
cache,
|
||||||
@ -124,13 +158,11 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
|
|||||||
buffer,
|
buffer,
|
||||||
priorityTaskManager,
|
priorityTaskManager,
|
||||||
C.PRIORITY_DOWNLOAD,
|
C.PRIORITY_DOWNLOAD,
|
||||||
cachingCounters,
|
progressNotifier,
|
||||||
isCanceled,
|
isCanceled,
|
||||||
true);
|
true);
|
||||||
downloadedSegments++;
|
if (progressNotifier != null) {
|
||||||
} finally {
|
progressNotifier.onSegmentDownloaded();
|
||||||
counters.newlyCachedBytes += cachingCounters.newlyCachedBytes;
|
|
||||||
updatePercentage();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -143,26 +175,6 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
|
|||||||
isCanceled.set(true);
|
isCanceled.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public final long getDownloadedBytes() {
|
|
||||||
return counters.totalCachedBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getTotalBytes() {
|
|
||||||
return counters.contentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final float getDownloadPercentage() {
|
|
||||||
return counters.percentage;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CachingCounters getCounters() {
|
|
||||||
return counters;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void remove() throws InterruptedException {
|
public final void remove() throws InterruptedException {
|
||||||
try {
|
try {
|
||||||
@ -199,64 +211,15 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
|
|||||||
* @param allowIncompleteList Whether to continue in the case that a load error prevents all
|
* @param allowIncompleteList Whether to continue in the case that a load error prevents all
|
||||||
* segments from being listed. If true then a partial segment list will be returned. If false
|
* segments from being listed. If true then a partial segment list will be returned. If false
|
||||||
* an {@link IOException} will be thrown.
|
* an {@link IOException} will be thrown.
|
||||||
|
* @return The list of downloadable {@link Segment}s.
|
||||||
* @throws InterruptedException Thrown if the thread was interrupted.
|
* @throws InterruptedException Thrown if the thread was interrupted.
|
||||||
* @throws IOException Thrown if {@code allowPartialIndex} is false and a load error occurs, or if
|
* @throws IOException Thrown if {@code allowPartialIndex} is false and a load error occurs, or if
|
||||||
* the media is not in a form that allows for its segments to be listed.
|
* the media is not in a form that allows for its segments to be listed.
|
||||||
* @return The list of downloadable {@link Segment}s.
|
|
||||||
*/
|
*/
|
||||||
protected abstract List<Segment> getSegments(
|
protected abstract List<Segment> getSegments(
|
||||||
DataSource dataSource, M manifest, boolean allowIncompleteList)
|
DataSource dataSource, M manifest, boolean allowIncompleteList)
|
||||||
throws InterruptedException, IOException;
|
throws InterruptedException, IOException;
|
||||||
|
|
||||||
/** Initializes the download, returning a list of {@link Segment}s that need to be downloaded. */
|
|
||||||
// Writes to downloadedSegments and downloadedBytes are safe. See the comment on download().
|
|
||||||
@SuppressWarnings("NonAtomicVolatileUpdate")
|
|
||||||
private List<Segment> initDownload() throws IOException, InterruptedException {
|
|
||||||
M manifest = getManifest(dataSource, manifestDataSpec);
|
|
||||||
if (!streamKeys.isEmpty()) {
|
|
||||||
manifest = manifest.copy(streamKeys);
|
|
||||||
}
|
|
||||||
List<Segment> segments = getSegments(dataSource, manifest, /* allowIncompleteList= */ false);
|
|
||||||
CachingCounters cachingCounters = new CachingCounters();
|
|
||||||
totalSegments = segments.size();
|
|
||||||
downloadedSegments = 0;
|
|
||||||
counters.alreadyCachedBytes = 0;
|
|
||||||
counters.newlyCachedBytes = 0;
|
|
||||||
long totalBytes = 0;
|
|
||||||
for (int i = segments.size() - 1; i >= 0; i--) {
|
|
||||||
Segment segment = segments.get(i);
|
|
||||||
CacheUtil.getCached(segment.dataSpec, cache, cacheKeyFactory, cachingCounters);
|
|
||||||
counters.alreadyCachedBytes += cachingCounters.alreadyCachedBytes;
|
|
||||||
if (cachingCounters.contentLength != C.LENGTH_UNSET) {
|
|
||||||
if (cachingCounters.alreadyCachedBytes == cachingCounters.contentLength) {
|
|
||||||
// The segment is fully downloaded.
|
|
||||||
downloadedSegments++;
|
|
||||||
segments.remove(i);
|
|
||||||
}
|
|
||||||
if (totalBytes != C.LENGTH_UNSET) {
|
|
||||||
totalBytes += cachingCounters.contentLength;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
totalBytes = C.LENGTH_UNSET;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
counters.contentLength = totalBytes;
|
|
||||||
updatePercentage();
|
|
||||||
return segments;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePercentage() {
|
|
||||||
counters.updatePercentage();
|
|
||||||
if (counters.percentage == C.PERCENTAGE_UNSET) {
|
|
||||||
int totalSegments = this.totalSegments;
|
|
||||||
int downloadedSegments = this.downloadedSegments;
|
|
||||||
if (totalSegments != C.LENGTH_UNSET && downloadedSegments != C.LENGTH_UNSET) {
|
|
||||||
counters.percentage =
|
|
||||||
totalSegments == 0 ? 100f : (downloadedSegments * 100f) / totalSegments;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeDataSpec(DataSpec dataSpec) {
|
private void removeDataSpec(DataSpec dataSpec) {
|
||||||
CacheUtil.remove(dataSpec, cache, cacheKeyFactory);
|
CacheUtil.remove(dataSpec, cache, cacheKeyFactory);
|
||||||
}
|
}
|
||||||
@ -269,4 +232,49 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
|
|||||||
/* key= */ null,
|
/* key= */ null,
|
||||||
/* flags= */ DataSpec.FLAG_ALLOW_GZIP);
|
/* flags= */ DataSpec.FLAG_ALLOW_GZIP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class ProgressNotifier implements CacheUtil.ProgressListener {
|
||||||
|
|
||||||
|
private final ProgressListener progressListener;
|
||||||
|
|
||||||
|
private final long contentLength;
|
||||||
|
private final int totalSegments;
|
||||||
|
|
||||||
|
private long bytesDownloaded;
|
||||||
|
private int segmentsDownloaded;
|
||||||
|
|
||||||
|
public ProgressNotifier(
|
||||||
|
ProgressListener progressListener,
|
||||||
|
long contentLength,
|
||||||
|
int totalSegments,
|
||||||
|
long bytesDownloaded,
|
||||||
|
int segmentsDownloaded) {
|
||||||
|
this.progressListener = progressListener;
|
||||||
|
this.contentLength = contentLength;
|
||||||
|
this.totalSegments = totalSegments;
|
||||||
|
this.bytesDownloaded = bytesDownloaded;
|
||||||
|
this.segmentsDownloaded = segmentsDownloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgress(long requestLength, long bytesCached, long newBytesCached) {
|
||||||
|
bytesDownloaded += newBytesCached;
|
||||||
|
progressListener.onProgress(contentLength, bytesDownloaded, getPercentDownloaded());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSegmentDownloaded() {
|
||||||
|
segmentsDownloaded++;
|
||||||
|
progressListener.onProgress(contentLength, bytesDownloaded, getPercentDownloaded());
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getPercentDownloaded() {
|
||||||
|
if (contentLength != C.LENGTH_UNSET && contentLength != 0) {
|
||||||
|
return (bytesDownloaded * 100f) / contentLength;
|
||||||
|
} else if (totalSegments != 0) {
|
||||||
|
return (segmentsDownloaded * 100f) / totalSegments;
|
||||||
|
} else {
|
||||||
|
return C.PERCENTAGE_UNSET;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.upstream.cache;
|
|||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||||
@ -31,36 +32,21 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
/**
|
/**
|
||||||
* Caching related utility methods.
|
* Caching related utility methods.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"NonAtomicVolatileUpdate", "NonAtomicOperationOnVolatileField"})
|
|
||||||
public final class CacheUtil {
|
public final class CacheUtil {
|
||||||
|
|
||||||
/** Counters used during caching. */
|
/** Receives progress updates during cache operations. */
|
||||||
public static class CachingCounters {
|
public interface ProgressListener {
|
||||||
/** The number of bytes already in the cache. */
|
|
||||||
public volatile long alreadyCachedBytes;
|
|
||||||
/** The number of newly cached bytes. */
|
|
||||||
public volatile long newlyCachedBytes;
|
|
||||||
/** The length of the content being cached in bytes, or {@link C#LENGTH_UNSET} if unknown. */
|
|
||||||
public volatile long contentLength = C.LENGTH_UNSET;
|
|
||||||
/** The percentage of cached data, or {@link C#PERCENTAGE_UNSET} if unavailable. */
|
|
||||||
public volatile float percentage;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the sum of {@link #alreadyCachedBytes} and {@link #newlyCachedBytes}.
|
* Called when progress is made during a cache operation.
|
||||||
|
*
|
||||||
|
* @param requestLength The length of the content being cached in bytes, or {@link
|
||||||
|
* C#LENGTH_UNSET} if unknown.
|
||||||
|
* @param bytesCached The number of bytes that are cached.
|
||||||
|
* @param newBytesCached The number of bytes that have been newly cached since the last progress
|
||||||
|
* update.
|
||||||
*/
|
*/
|
||||||
public long totalCachedBytes() {
|
void onProgress(long requestLength, long bytesCached, long newBytesCached);
|
||||||
return alreadyCachedBytes + newlyCachedBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Updates {@link #percentage} value using other values. */
|
|
||||||
public void updatePercentage() {
|
|
||||||
// Take local snapshot of the volatile field
|
|
||||||
long contentLength = this.contentLength;
|
|
||||||
percentage =
|
|
||||||
contentLength == C.LENGTH_UNSET
|
|
||||||
? C.PERCENTAGE_UNSET
|
|
||||||
: ((totalCachedBytes() * 100f) / contentLength);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Default buffer size to be used while caching. */
|
/** Default buffer size to be used while caching. */
|
||||||
@ -80,48 +66,43 @@ public final class CacheUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a {@link CachingCounters} to contain the number of bytes already downloaded and the length
|
* Queries the cache to obtain the request length and the number of bytes already cached for a
|
||||||
* for the content defined by a {@code dataSpec}. {@link CachingCounters#newlyCachedBytes} is
|
* given {@link DataSpec}.
|
||||||
* reset to 0.
|
|
||||||
*
|
*
|
||||||
* @param dataSpec Defines the data to be checked.
|
* @param dataSpec Defines the data to be checked.
|
||||||
* @param cache A {@link Cache} which has the data.
|
* @param cache A {@link Cache} which has the data.
|
||||||
* @param cacheKeyFactory An optional factory for cache keys.
|
* @param cacheKeyFactory An optional factory for cache keys.
|
||||||
* @param counters The {@link CachingCounters} to update.
|
* @return A pair containing the request length and the number of bytes that are already cached.
|
||||||
*/
|
*/
|
||||||
public static void getCached(
|
public static Pair<Long, Long> getCached(
|
||||||
DataSpec dataSpec,
|
DataSpec dataSpec, Cache cache, @Nullable CacheKeyFactory cacheKeyFactory) {
|
||||||
Cache cache,
|
|
||||||
@Nullable CacheKeyFactory cacheKeyFactory,
|
|
||||||
CachingCounters counters) {
|
|
||||||
String key = buildCacheKey(dataSpec, cacheKeyFactory);
|
String key = buildCacheKey(dataSpec, cacheKeyFactory);
|
||||||
long position = dataSpec.absoluteStreamPosition;
|
long position = dataSpec.absoluteStreamPosition;
|
||||||
long bytesLeft;
|
long requestLength;
|
||||||
if (dataSpec.length != C.LENGTH_UNSET) {
|
if (dataSpec.length != C.LENGTH_UNSET) {
|
||||||
bytesLeft = dataSpec.length;
|
requestLength = dataSpec.length;
|
||||||
} else {
|
} else {
|
||||||
long contentLength = ContentMetadata.getContentLength(cache.getContentMetadata(key));
|
long contentLength = ContentMetadata.getContentLength(cache.getContentMetadata(key));
|
||||||
bytesLeft = contentLength == C.LENGTH_UNSET ? C.LENGTH_UNSET : contentLength - position;
|
requestLength = contentLength == C.LENGTH_UNSET ? C.LENGTH_UNSET : contentLength - position;
|
||||||
}
|
}
|
||||||
counters.contentLength = bytesLeft;
|
long bytesAlreadyCached = 0;
|
||||||
counters.alreadyCachedBytes = 0;
|
long bytesLeft = requestLength;
|
||||||
counters.newlyCachedBytes = 0;
|
|
||||||
while (bytesLeft != 0) {
|
while (bytesLeft != 0) {
|
||||||
long blockLength =
|
long blockLength =
|
||||||
cache.getCachedLength(
|
cache.getCachedLength(
|
||||||
key, position, bytesLeft != C.LENGTH_UNSET ? bytesLeft : Long.MAX_VALUE);
|
key, position, bytesLeft != C.LENGTH_UNSET ? bytesLeft : Long.MAX_VALUE);
|
||||||
if (blockLength > 0) {
|
if (blockLength > 0) {
|
||||||
counters.alreadyCachedBytes += blockLength;
|
bytesAlreadyCached += blockLength;
|
||||||
} else {
|
} else {
|
||||||
blockLength = -blockLength;
|
blockLength = -blockLength;
|
||||||
if (blockLength == Long.MAX_VALUE) {
|
if (blockLength == Long.MAX_VALUE) {
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
position += blockLength;
|
position += blockLength;
|
||||||
bytesLeft -= bytesLeft == C.LENGTH_UNSET ? 0 : blockLength;
|
bytesLeft -= bytesLeft == C.LENGTH_UNSET ? 0 : blockLength;
|
||||||
}
|
}
|
||||||
counters.updatePercentage();
|
return Pair.create(requestLength, bytesAlreadyCached);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -132,7 +113,7 @@ public final class CacheUtil {
|
|||||||
* @param cache A {@link Cache} to store the data.
|
* @param cache A {@link Cache} to store the data.
|
||||||
* @param cacheKeyFactory An optional factory for cache keys.
|
* @param cacheKeyFactory An optional factory for cache keys.
|
||||||
* @param upstream A {@link DataSource} for reading data not in the cache.
|
* @param upstream A {@link DataSource} for reading data not in the cache.
|
||||||
* @param counters If not null, updated during caching.
|
* @param progressListener A listener to receive progress updates, or {@code null}.
|
||||||
* @param isCanceled An optional flag that will interrupt caching if set to true.
|
* @param isCanceled An optional flag that will interrupt caching if set to true.
|
||||||
* @throws IOException If an error occurs reading from the source.
|
* @throws IOException If an error occurs reading from the source.
|
||||||
* @throws InterruptedException If the thread was interrupted directly or via {@code isCanceled}.
|
* @throws InterruptedException If the thread was interrupted directly or via {@code isCanceled}.
|
||||||
@ -142,7 +123,7 @@ public final class CacheUtil {
|
|||||||
Cache cache,
|
Cache cache,
|
||||||
@Nullable CacheKeyFactory cacheKeyFactory,
|
@Nullable CacheKeyFactory cacheKeyFactory,
|
||||||
DataSource upstream,
|
DataSource upstream,
|
||||||
@Nullable CachingCounters counters,
|
@Nullable ProgressListener progressListener,
|
||||||
@Nullable AtomicBoolean isCanceled)
|
@Nullable AtomicBoolean isCanceled)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
cache(
|
cache(
|
||||||
@ -153,7 +134,7 @@ public final class CacheUtil {
|
|||||||
new byte[DEFAULT_BUFFER_SIZE_BYTES],
|
new byte[DEFAULT_BUFFER_SIZE_BYTES],
|
||||||
/* priorityTaskManager= */ null,
|
/* priorityTaskManager= */ null,
|
||||||
/* priority= */ 0,
|
/* priority= */ 0,
|
||||||
counters,
|
progressListener,
|
||||||
isCanceled,
|
isCanceled,
|
||||||
/* enableEOFException= */ false);
|
/* enableEOFException= */ false);
|
||||||
}
|
}
|
||||||
@ -176,7 +157,7 @@ public final class CacheUtil {
|
|||||||
* @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with
|
* @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with
|
||||||
* caching.
|
* caching.
|
||||||
* @param priority The priority of this task. Used with {@code priorityTaskManager}.
|
* @param priority The priority of this task. Used with {@code priorityTaskManager}.
|
||||||
* @param counters If not null, updated during caching.
|
* @param progressListener A listener to receive progress updates, or {@code null}.
|
||||||
* @param isCanceled An optional flag that will interrupt caching if set to true.
|
* @param isCanceled An optional flag that will interrupt caching if set to true.
|
||||||
* @param enableEOFException Whether to throw an {@link EOFException} if end of input has been
|
* @param enableEOFException Whether to throw an {@link EOFException} if end of input has been
|
||||||
* reached unexpectedly.
|
* reached unexpectedly.
|
||||||
@ -191,19 +172,18 @@ public final class CacheUtil {
|
|||||||
byte[] buffer,
|
byte[] buffer,
|
||||||
PriorityTaskManager priorityTaskManager,
|
PriorityTaskManager priorityTaskManager,
|
||||||
int priority,
|
int priority,
|
||||||
@Nullable CachingCounters counters,
|
@Nullable ProgressListener progressListener,
|
||||||
@Nullable AtomicBoolean isCanceled,
|
@Nullable AtomicBoolean isCanceled,
|
||||||
boolean enableEOFException)
|
boolean enableEOFException)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
Assertions.checkNotNull(dataSource);
|
Assertions.checkNotNull(dataSource);
|
||||||
Assertions.checkNotNull(buffer);
|
Assertions.checkNotNull(buffer);
|
||||||
|
|
||||||
if (counters != null) {
|
ProgressNotifier progressNotifier = null;
|
||||||
// Initialize the CachingCounter values.
|
if (progressListener != null) {
|
||||||
getCached(dataSpec, cache, cacheKeyFactory, counters);
|
progressNotifier = new ProgressNotifier(progressListener);
|
||||||
} else {
|
Pair<Long, Long> lengthAndBytesAlreadyCached = getCached(dataSpec, cache, cacheKeyFactory);
|
||||||
// Dummy CachingCounters. No need to initialize as they will not be visible to the caller.
|
progressNotifier.init(lengthAndBytesAlreadyCached.first, lengthAndBytesAlreadyCached.second);
|
||||||
counters = new CachingCounters();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String key = buildCacheKey(dataSpec, cacheKeyFactory);
|
String key = buildCacheKey(dataSpec, cacheKeyFactory);
|
||||||
@ -234,7 +214,7 @@ public final class CacheUtil {
|
|||||||
buffer,
|
buffer,
|
||||||
priorityTaskManager,
|
priorityTaskManager,
|
||||||
priority,
|
priority,
|
||||||
counters,
|
progressNotifier,
|
||||||
isCanceled);
|
isCanceled);
|
||||||
if (read < blockLength) {
|
if (read < blockLength) {
|
||||||
// Reached to the end of the data.
|
// Reached to the end of the data.
|
||||||
@ -261,7 +241,7 @@ public final class CacheUtil {
|
|||||||
* @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with
|
* @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with
|
||||||
* caching.
|
* caching.
|
||||||
* @param priority The priority of this task.
|
* @param priority The priority of this task.
|
||||||
* @param counters Counters to be set during reading.
|
* @param progressNotifier A notifier through which to report progress updates, or {@code null}.
|
||||||
* @param isCanceled An optional flag that will interrupt caching if set to true.
|
* @param isCanceled An optional flag that will interrupt caching if set to true.
|
||||||
* @return Number of read bytes, or 0 if no data is available because the end of the opened range
|
* @return Number of read bytes, or 0 if no data is available because the end of the opened range
|
||||||
* has been reached.
|
* has been reached.
|
||||||
@ -274,7 +254,7 @@ public final class CacheUtil {
|
|||||||
byte[] buffer,
|
byte[] buffer,
|
||||||
PriorityTaskManager priorityTaskManager,
|
PriorityTaskManager priorityTaskManager,
|
||||||
int priority,
|
int priority,
|
||||||
CachingCounters counters,
|
@Nullable ProgressNotifier progressNotifier,
|
||||||
AtomicBoolean isCanceled)
|
AtomicBoolean isCanceled)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
long positionOffset = absoluteStreamPosition - dataSpec.absoluteStreamPosition;
|
long positionOffset = absoluteStreamPosition - dataSpec.absoluteStreamPosition;
|
||||||
@ -298,8 +278,8 @@ public final class CacheUtil {
|
|||||||
dataSpec.key,
|
dataSpec.key,
|
||||||
dataSpec.flags);
|
dataSpec.flags);
|
||||||
long resolvedLength = dataSource.open(dataSpec);
|
long resolvedLength = dataSource.open(dataSpec);
|
||||||
if (counters.contentLength == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) {
|
if (progressNotifier != null && resolvedLength != C.LENGTH_UNSET) {
|
||||||
counters.contentLength = positionOffset + resolvedLength;
|
progressNotifier.onRequestLengthResolved(positionOffset + resolvedLength);
|
||||||
}
|
}
|
||||||
long totalBytesRead = 0;
|
long totalBytesRead = 0;
|
||||||
while (totalBytesRead != length) {
|
while (totalBytesRead != length) {
|
||||||
@ -312,14 +292,15 @@ public final class CacheUtil {
|
|||||||
? (int) Math.min(buffer.length, length - totalBytesRead)
|
? (int) Math.min(buffer.length, length - totalBytesRead)
|
||||||
: buffer.length);
|
: buffer.length);
|
||||||
if (bytesRead == C.RESULT_END_OF_INPUT) {
|
if (bytesRead == C.RESULT_END_OF_INPUT) {
|
||||||
if (counters.contentLength == C.LENGTH_UNSET) {
|
if (progressNotifier != null) {
|
||||||
counters.contentLength = positionOffset + totalBytesRead;
|
progressNotifier.onRequestLengthResolved(positionOffset + totalBytesRead);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
totalBytesRead += bytesRead;
|
totalBytesRead += bytesRead;
|
||||||
counters.newlyCachedBytes += bytesRead;
|
if (progressNotifier != null) {
|
||||||
counters.updatePercentage();
|
progressNotifier.onBytesCached(bytesRead);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return totalBytesRead;
|
return totalBytesRead;
|
||||||
} catch (PriorityTaskManager.PriorityTooLowException exception) {
|
} catch (PriorityTaskManager.PriorityTooLowException exception) {
|
||||||
@ -374,4 +355,34 @@ public final class CacheUtil {
|
|||||||
|
|
||||||
private CacheUtil() {}
|
private CacheUtil() {}
|
||||||
|
|
||||||
|
private static final class ProgressNotifier {
|
||||||
|
/** The listener to notify when progress is made. */
|
||||||
|
private final ProgressListener listener;
|
||||||
|
/** The length of the content being cached in bytes, or {@link C#LENGTH_UNSET} if unknown. */
|
||||||
|
private long requestLength;
|
||||||
|
/** The number of bytes that are cached. */
|
||||||
|
private long bytesCached;
|
||||||
|
|
||||||
|
public ProgressNotifier(ProgressListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(long requestLength, long bytesCached) {
|
||||||
|
this.requestLength = requestLength;
|
||||||
|
this.bytesCached = bytesCached;
|
||||||
|
listener.onProgress(requestLength, bytesCached, /* newBytesCached= */ 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRequestLengthResolved(long requestLength) {
|
||||||
|
if (this.requestLength == C.LENGTH_UNSET && requestLength != C.LENGTH_UNSET) {
|
||||||
|
this.requestLength = requestLength;
|
||||||
|
listener.onProgress(requestLength, bytesCached, /* newBytesCached= */ 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onBytesCached(long newBytesCached) {
|
||||||
|
bytesCached += newBytesCached;
|
||||||
|
listener.onProgress(requestLength, bytesCached, newBytesCached);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,9 +76,9 @@ public class DefaultDownloadIndexTest {
|
|||||||
.setUri("different uri")
|
.setUri("different uri")
|
||||||
.setCacheKey("different cacheKey")
|
.setCacheKey("different cacheKey")
|
||||||
.setState(Download.STATE_FAILED)
|
.setState(Download.STATE_FAILED)
|
||||||
.setDownloadPercentage(50)
|
.setPercentDownloaded(50)
|
||||||
.setDownloadedBytes(200)
|
.setBytesDownloaded(200)
|
||||||
.setTotalBytes(400)
|
.setContentLength(400)
|
||||||
.setFailureReason(Download.FAILURE_REASON_UNKNOWN)
|
.setFailureReason(Download.FAILURE_REASON_UNKNOWN)
|
||||||
.setStopReason(0x12345678)
|
.setStopReason(0x12345678)
|
||||||
.setStartTimeMs(10)
|
.setStartTimeMs(10)
|
||||||
@ -300,10 +300,10 @@ public class DefaultDownloadIndexTest {
|
|||||||
assertThat(download.state).isEqualTo(that.state);
|
assertThat(download.state).isEqualTo(that.state);
|
||||||
assertThat(download.startTimeMs).isEqualTo(that.startTimeMs);
|
assertThat(download.startTimeMs).isEqualTo(that.startTimeMs);
|
||||||
assertThat(download.updateTimeMs).isEqualTo(that.updateTimeMs);
|
assertThat(download.updateTimeMs).isEqualTo(that.updateTimeMs);
|
||||||
assertThat(download.failureReason).isEqualTo(that.failureReason);
|
assertThat(download.contentLength).isEqualTo(that.contentLength);
|
||||||
assertThat(download.stopReason).isEqualTo(that.stopReason);
|
assertThat(download.stopReason).isEqualTo(that.stopReason);
|
||||||
assertThat(download.getDownloadPercentage()).isEqualTo(that.getDownloadPercentage());
|
assertThat(download.failureReason).isEqualTo(that.failureReason);
|
||||||
assertThat(download.getDownloadedBytes()).isEqualTo(that.getDownloadedBytes());
|
assertThat(download.getPercentDownloaded()).isEqualTo(that.getPercentDownloaded());
|
||||||
assertThat(download.getTotalBytes()).isEqualTo(that.getTotalBytes());
|
assertThat(download.getBytesDownloaded()).isEqualTo(that.getBytesDownloaded());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ package com.google.android.exoplayer2.offline;
|
|||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters;
|
import com.google.android.exoplayer2.C;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -29,52 +29,61 @@ import java.util.List;
|
|||||||
* creation for tests. Tests must avoid depending on the default values but explicitly set tested
|
* creation for tests. Tests must avoid depending on the default values but explicitly set tested
|
||||||
* parameters during test initialization.
|
* parameters during test initialization.
|
||||||
*/
|
*/
|
||||||
class DownloadBuilder {
|
/* package */ final class DownloadBuilder {
|
||||||
private final CachingCounters counters;
|
|
||||||
|
private final DownloadProgress progress;
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
private String type;
|
private String type;
|
||||||
private Uri uri;
|
private Uri uri;
|
||||||
@Nullable private String cacheKey;
|
|
||||||
private int state;
|
|
||||||
private int failureReason;
|
|
||||||
private int stopReason;
|
|
||||||
private long startTimeMs;
|
|
||||||
private long updateTimeMs;
|
|
||||||
private List<StreamKey> streamKeys;
|
private List<StreamKey> streamKeys;
|
||||||
|
@Nullable private String cacheKey;
|
||||||
private byte[] customMetadata;
|
private byte[] customMetadata;
|
||||||
|
|
||||||
DownloadBuilder(String id) {
|
private int state;
|
||||||
this(id, "type", Uri.parse("uri"), /* cacheKey= */ null, new byte[0], Collections.emptyList());
|
private long startTimeMs;
|
||||||
|
private long updateTimeMs;
|
||||||
|
private long contentLength;
|
||||||
|
private int stopReason;
|
||||||
|
private int failureReason;
|
||||||
|
|
||||||
|
/* package */ DownloadBuilder(String id) {
|
||||||
|
this(
|
||||||
|
id,
|
||||||
|
"type",
|
||||||
|
Uri.parse("uri"),
|
||||||
|
/* streamKeys= */ Collections.emptyList(),
|
||||||
|
/* cacheKey= */ null,
|
||||||
|
new byte[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadBuilder(DownloadRequest request) {
|
/* package */ DownloadBuilder(DownloadRequest request) {
|
||||||
this(
|
this(
|
||||||
request.id,
|
request.id,
|
||||||
request.type,
|
request.type,
|
||||||
request.uri,
|
request.uri,
|
||||||
|
request.streamKeys,
|
||||||
request.customCacheKey,
|
request.customCacheKey,
|
||||||
request.data,
|
request.data);
|
||||||
request.streamKeys);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadBuilder(
|
/* package */ DownloadBuilder(
|
||||||
String id,
|
String id,
|
||||||
String type,
|
String type,
|
||||||
Uri uri,
|
Uri uri,
|
||||||
|
List<StreamKey> streamKeys,
|
||||||
String cacheKey,
|
String cacheKey,
|
||||||
byte[] customMetadata,
|
byte[] customMetadata) {
|
||||||
List<StreamKey> streamKeys) {
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
this.cacheKey = cacheKey;
|
|
||||||
this.state = Download.STATE_QUEUED;
|
|
||||||
this.failureReason = Download.FAILURE_REASON_NONE;
|
|
||||||
this.startTimeMs = (long) 0;
|
|
||||||
this.updateTimeMs = (long) 0;
|
|
||||||
this.streamKeys = streamKeys;
|
this.streamKeys = streamKeys;
|
||||||
|
this.cacheKey = cacheKey;
|
||||||
this.customMetadata = customMetadata;
|
this.customMetadata = customMetadata;
|
||||||
this.counters = new CachingCounters();
|
this.state = Download.STATE_QUEUED;
|
||||||
|
this.contentLength = C.LENGTH_UNSET;
|
||||||
|
this.failureReason = Download.FAILURE_REASON_NONE;
|
||||||
|
this.progress = new DownloadProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadBuilder setId(String id) {
|
public DownloadBuilder setId(String id) {
|
||||||
@ -107,18 +116,18 @@ class DownloadBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadBuilder setDownloadPercentage(float downloadPercentage) {
|
public DownloadBuilder setPercentDownloaded(float percentDownloaded) {
|
||||||
counters.percentage = downloadPercentage;
|
progress.percentDownloaded = percentDownloaded;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadBuilder setDownloadedBytes(long downloadedBytes) {
|
public DownloadBuilder setBytesDownloaded(long bytesDownloaded) {
|
||||||
counters.alreadyCachedBytes = downloadedBytes;
|
progress.bytesDownloaded = bytesDownloaded;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadBuilder setTotalBytes(long totalBytes) {
|
public DownloadBuilder setContentLength(long contentLength) {
|
||||||
counters.contentLength = totalBytes;
|
this.contentLength = contentLength;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,6 +165,13 @@ class DownloadBuilder {
|
|||||||
DownloadRequest request =
|
DownloadRequest request =
|
||||||
new DownloadRequest(id, type, uri, streamKeys, cacheKey, customMetadata);
|
new DownloadRequest(id, type, uri, streamKeys, cacheKey, customMetadata);
|
||||||
return new Download(
|
return new Download(
|
||||||
request, state, failureReason, stopReason, startTimeMs, updateTimeMs, counters);
|
request,
|
||||||
|
state,
|
||||||
|
startTimeMs,
|
||||||
|
updateTimeMs,
|
||||||
|
contentLength,
|
||||||
|
stopReason,
|
||||||
|
failureReason,
|
||||||
|
progress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,13 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.offline.Download.State;
|
import com.google.android.exoplayer2.offline.Download.State;
|
||||||
import com.google.android.exoplayer2.scheduler.Requirements;
|
import com.google.android.exoplayer2.scheduler.Requirements;
|
||||||
import com.google.android.exoplayer2.testutil.DummyMainThread;
|
import com.google.android.exoplayer2.testutil.DummyMainThread;
|
||||||
import com.google.android.exoplayer2.testutil.DummyMainThread.TestRunnable;
|
import com.google.android.exoplayer2.testutil.DummyMainThread.TestRunnable;
|
||||||
import com.google.android.exoplayer2.testutil.TestDownloadManagerListener;
|
import com.google.android.exoplayer2.testutil.TestDownloadManagerListener;
|
||||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -184,7 +184,7 @@ public class DownloadManagerTest {
|
|||||||
|
|
||||||
int tooManyRetries = MIN_RETRY_COUNT + 10;
|
int tooManyRetries = MIN_RETRY_COUNT + 10;
|
||||||
for (int i = 0; i < tooManyRetries; i++) {
|
for (int i = 0; i < tooManyRetries; i++) {
|
||||||
downloader.increaseDownloadedByteCount();
|
downloader.incrementBytesDownloaded();
|
||||||
downloader.assertStarted(MAX_RETRY_DELAY).fail();
|
downloader.assertStarted(MAX_RETRY_DELAY).fail();
|
||||||
}
|
}
|
||||||
downloader.assertStarted(MAX_RETRY_DELAY).unblock();
|
downloader.assertStarted(MAX_RETRY_DELAY).unblock();
|
||||||
@ -555,11 +555,11 @@ public class DownloadManagerTest {
|
|||||||
private static void assertEqualIgnoringTimeFields(Download download, Download that) {
|
private static void assertEqualIgnoringTimeFields(Download download, Download that) {
|
||||||
assertThat(download.request).isEqualTo(that.request);
|
assertThat(download.request).isEqualTo(that.request);
|
||||||
assertThat(download.state).isEqualTo(that.state);
|
assertThat(download.state).isEqualTo(that.state);
|
||||||
|
assertThat(download.contentLength).isEqualTo(that.contentLength);
|
||||||
assertThat(download.failureReason).isEqualTo(that.failureReason);
|
assertThat(download.failureReason).isEqualTo(that.failureReason);
|
||||||
assertThat(download.stopReason).isEqualTo(that.stopReason);
|
assertThat(download.stopReason).isEqualTo(that.stopReason);
|
||||||
assertThat(download.getDownloadPercentage()).isEqualTo(that.getDownloadPercentage());
|
assertThat(download.getPercentDownloaded()).isEqualTo(that.getPercentDownloaded());
|
||||||
assertThat(download.getDownloadedBytes()).isEqualTo(that.getDownloadedBytes());
|
assertThat(download.getBytesDownloaded()).isEqualTo(that.getBytesDownloaded());
|
||||||
assertThat(download.getTotalBytes()).isEqualTo(that.getTotalBytes());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DownloadRequest createDownloadRequest() {
|
private static DownloadRequest createDownloadRequest() {
|
||||||
@ -722,21 +722,23 @@ public class DownloadManagerTest {
|
|||||||
private volatile boolean cancelled;
|
private volatile boolean cancelled;
|
||||||
private volatile boolean enableDownloadIOException;
|
private volatile boolean enableDownloadIOException;
|
||||||
private volatile int startCount;
|
private volatile int startCount;
|
||||||
private CachingCounters counters;
|
private volatile int bytesDownloaded;
|
||||||
|
|
||||||
private FakeDownloader() {
|
private FakeDownloader() {
|
||||||
this.started = new CountDownLatch(1);
|
this.started = new CountDownLatch(1);
|
||||||
this.blocker = new com.google.android.exoplayer2.util.ConditionVariable();
|
this.blocker = new com.google.android.exoplayer2.util.ConditionVariable();
|
||||||
counters = new CachingCounters();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"NonAtomicOperationOnVolatileField", "NonAtomicVolatileUpdate"})
|
@SuppressWarnings({"NonAtomicOperationOnVolatileField", "NonAtomicVolatileUpdate"})
|
||||||
@Override
|
@Override
|
||||||
public void download() throws InterruptedException, IOException {
|
public void download(ProgressListener listener) throws InterruptedException, IOException {
|
||||||
// It's ok to update this directly as no other thread will update it.
|
// It's ok to update this directly as no other thread will update it.
|
||||||
startCount++;
|
startCount++;
|
||||||
started.countDown();
|
started.countDown();
|
||||||
block();
|
block();
|
||||||
|
if (bytesDownloaded > 0) {
|
||||||
|
listener.onProgress(C.LENGTH_UNSET, bytesDownloaded, C.PERCENTAGE_UNSET);
|
||||||
|
}
|
||||||
if (enableDownloadIOException) {
|
if (enableDownloadIOException) {
|
||||||
enableDownloadIOException = false;
|
enableDownloadIOException = false;
|
||||||
throw new IOException();
|
throw new IOException();
|
||||||
@ -783,7 +785,7 @@ public class DownloadManagerTest {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FakeDownloader assertStartCount(int count) throws InterruptedException {
|
private FakeDownloader assertStartCount(int count) {
|
||||||
assertThat(startCount).isEqualTo(count);
|
assertThat(startCount).isEqualTo(count);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -823,34 +825,14 @@ public class DownloadManagerTest {
|
|||||||
return unblock();
|
return unblock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getDownloadedBytes() {
|
|
||||||
return counters.newlyCachedBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getTotalBytes() {
|
|
||||||
return counters.contentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getDownloadPercentage() {
|
|
||||||
return counters.percentage;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CachingCounters getCounters() {
|
|
||||||
return counters;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertDoesNotStart() throws InterruptedException {
|
private void assertDoesNotStart() throws InterruptedException {
|
||||||
Thread.sleep(ASSERT_FALSE_TIME);
|
Thread.sleep(ASSERT_FALSE_TIME);
|
||||||
assertThat(started.getCount()).isEqualTo(1);
|
assertThat(started.getCount()).isEqualTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"NonAtomicOperationOnVolatileField", "NonAtomicVolatileUpdate"})
|
@SuppressWarnings({"NonAtomicOperationOnVolatileField", "NonAtomicVolatileUpdate"})
|
||||||
private void increaseDownloadedByteCount() {
|
private void incrementBytesDownloaded() {
|
||||||
counters.newlyCachedBytes++;
|
bytesDownloaded++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -343,7 +343,7 @@ public final class CacheDataSourceTest {
|
|||||||
cache,
|
cache,
|
||||||
/* cacheKeyFactory= */ null,
|
/* cacheKeyFactory= */ null,
|
||||||
upstream2,
|
upstream2,
|
||||||
/* counters= */ null,
|
/* progressListener= */ null,
|
||||||
/* isCanceled= */ null);
|
/* isCanceled= */ null);
|
||||||
|
|
||||||
// Read the rest of the data.
|
// Read the rest of the data.
|
||||||
@ -392,7 +392,7 @@ public final class CacheDataSourceTest {
|
|||||||
cache,
|
cache,
|
||||||
/* cacheKeyFactory= */ null,
|
/* cacheKeyFactory= */ null,
|
||||||
upstream2,
|
upstream2,
|
||||||
/* counters= */ null,
|
/* progressListener= */ null,
|
||||||
/* isCanceled= */ null);
|
/* isCanceled= */ null);
|
||||||
|
|
||||||
// Read the rest of the data.
|
// Read the rest of the data.
|
||||||
@ -416,7 +416,7 @@ public final class CacheDataSourceTest {
|
|||||||
cache,
|
cache,
|
||||||
/* cacheKeyFactory= */ null,
|
/* cacheKeyFactory= */ null,
|
||||||
upstream,
|
upstream,
|
||||||
/* counters= */ null,
|
/* progressListener= */ null,
|
||||||
/* isCanceled= */ null);
|
/* isCanceled= */ null);
|
||||||
|
|
||||||
// Create cache read-only CacheDataSource.
|
// Create cache read-only CacheDataSource.
|
||||||
@ -452,7 +452,7 @@ public final class CacheDataSourceTest {
|
|||||||
cache,
|
cache,
|
||||||
/* cacheKeyFactory= */ null,
|
/* cacheKeyFactory= */ null,
|
||||||
upstream,
|
upstream,
|
||||||
/* counters= */ null,
|
/* progressListener= */ null,
|
||||||
/* isCanceled= */ null);
|
/* isCanceled= */ null);
|
||||||
|
|
||||||
// Create blocking CacheDataSource.
|
// Create blocking CacheDataSource.
|
||||||
|
@ -22,6 +22,7 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.util.Pair;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
@ -30,7 +31,6 @@ import com.google.android.exoplayer2.testutil.FakeDataSource;
|
|||||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||||
import com.google.android.exoplayer2.upstream.FileDataSource;
|
import com.google.android.exoplayer2.upstream.FileDataSource;
|
||||||
import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters;
|
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -100,12 +100,12 @@ public final class CacheUtilTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() throws Exception {
|
public void tearDown() {
|
||||||
Util.recursiveDelete(tempFolder);
|
Util.recursiveDelete(tempFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGenerateKey() throws Exception {
|
public void testGenerateKey() {
|
||||||
assertThat(CacheUtil.generateKey(Uri.EMPTY)).isNotNull();
|
assertThat(CacheUtil.generateKey(Uri.EMPTY)).isNotNull();
|
||||||
|
|
||||||
Uri testUri = Uri.parse("test");
|
Uri testUri = Uri.parse("test");
|
||||||
@ -120,7 +120,7 @@ public final class CacheUtilTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDefaultCacheKeyFactory_buildCacheKey() throws Exception {
|
public void testDefaultCacheKeyFactory_buildCacheKey() {
|
||||||
Uri testUri = Uri.parse("test");
|
Uri testUri = Uri.parse("test");
|
||||||
String key = "key";
|
String key = "key";
|
||||||
// If DataSpec.key is present, returns it.
|
// If DataSpec.key is present, returns it.
|
||||||
@ -136,51 +136,55 @@ public final class CacheUtilTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCachedNoData() throws Exception {
|
public void testGetCachedNoData() {
|
||||||
CachingCounters counters = new CachingCounters();
|
Pair<Long, Long> contentLengthAndBytesCached =
|
||||||
CacheUtil.getCached(
|
CacheUtil.getCached(
|
||||||
new DataSpec(Uri.parse("test")), mockCache, /* cacheKeyFactory= */ null, counters);
|
new DataSpec(Uri.parse("test")), mockCache, /* cacheKeyFactory= */ null);
|
||||||
|
|
||||||
assertCounters(counters, 0, 0, C.LENGTH_UNSET);
|
assertThat(contentLengthAndBytesCached.first).isEqualTo(C.LENGTH_UNSET);
|
||||||
|
assertThat(contentLengthAndBytesCached.second).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCachedDataUnknownLength() throws Exception {
|
public void testGetCachedDataUnknownLength() {
|
||||||
// Mock there is 100 bytes cached at the beginning
|
// Mock there is 100 bytes cached at the beginning
|
||||||
mockCache.spansAndGaps = new int[] {100};
|
mockCache.spansAndGaps = new int[] {100};
|
||||||
CachingCounters counters = new CachingCounters();
|
Pair<Long, Long> contentLengthAndBytesCached =
|
||||||
CacheUtil.getCached(
|
CacheUtil.getCached(
|
||||||
new DataSpec(Uri.parse("test")), mockCache, /* cacheKeyFactory= */ null, counters);
|
new DataSpec(Uri.parse("test")), mockCache, /* cacheKeyFactory= */ null);
|
||||||
|
|
||||||
assertCounters(counters, 100, 0, C.LENGTH_UNSET);
|
assertThat(contentLengthAndBytesCached.first).isEqualTo(C.LENGTH_UNSET);
|
||||||
|
assertThat(contentLengthAndBytesCached.second).isEqualTo(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCachedNoDataKnownLength() throws Exception {
|
public void testGetCachedNoDataKnownLength() {
|
||||||
mockCache.contentLength = 1000;
|
mockCache.contentLength = 1000;
|
||||||
CachingCounters counters = new CachingCounters();
|
Pair<Long, Long> contentLengthAndBytesCached =
|
||||||
CacheUtil.getCached(
|
CacheUtil.getCached(
|
||||||
new DataSpec(Uri.parse("test")), mockCache, /* cacheKeyFactory= */ null, counters);
|
new DataSpec(Uri.parse("test")), mockCache, /* cacheKeyFactory= */ null);
|
||||||
|
|
||||||
assertCounters(counters, 0, 0, 1000);
|
assertThat(contentLengthAndBytesCached.first).isEqualTo(1000);
|
||||||
|
assertThat(contentLengthAndBytesCached.second).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCached() throws Exception {
|
public void testGetCached() {
|
||||||
mockCache.contentLength = 1000;
|
mockCache.contentLength = 1000;
|
||||||
mockCache.spansAndGaps = new int[] {100, 100, 200};
|
mockCache.spansAndGaps = new int[] {100, 100, 200};
|
||||||
CachingCounters counters = new CachingCounters();
|
Pair<Long, Long> contentLengthAndBytesCached =
|
||||||
CacheUtil.getCached(
|
CacheUtil.getCached(
|
||||||
new DataSpec(Uri.parse("test")), mockCache, /* cacheKeyFactory= */ null, counters);
|
new DataSpec(Uri.parse("test")), mockCache, /* cacheKeyFactory= */ null);
|
||||||
|
|
||||||
assertCounters(counters, 300, 0, 1000);
|
assertThat(contentLengthAndBytesCached.first).isEqualTo(1000);
|
||||||
|
assertThat(contentLengthAndBytesCached.second).isEqualTo(300);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCachedFromNonZeroPosition() throws Exception {
|
public void testGetCachedFromNonZeroPosition() {
|
||||||
mockCache.contentLength = 1000;
|
mockCache.contentLength = 1000;
|
||||||
mockCache.spansAndGaps = new int[] {100, 100, 200};
|
mockCache.spansAndGaps = new int[] {100, 100, 200};
|
||||||
CachingCounters counters = new CachingCounters();
|
Pair<Long, Long> contentLengthAndBytesCached =
|
||||||
CacheUtil.getCached(
|
CacheUtil.getCached(
|
||||||
new DataSpec(
|
new DataSpec(
|
||||||
Uri.parse("test"),
|
Uri.parse("test"),
|
||||||
@ -188,10 +192,10 @@ public final class CacheUtilTest {
|
|||||||
/* length= */ C.LENGTH_UNSET,
|
/* length= */ C.LENGTH_UNSET,
|
||||||
/* key= */ null),
|
/* key= */ null),
|
||||||
mockCache,
|
mockCache,
|
||||||
/* cacheKeyFactory= */ null,
|
/* cacheKeyFactory= */ null);
|
||||||
counters);
|
|
||||||
|
|
||||||
assertCounters(counters, 200, 0, 900);
|
assertThat(contentLengthAndBytesCached.first).isEqualTo(900);
|
||||||
|
assertThat(contentLengthAndBytesCached.second).isEqualTo(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -208,7 +212,7 @@ public final class CacheUtilTest {
|
|||||||
counters,
|
counters,
|
||||||
/* isCanceled= */ null);
|
/* isCanceled= */ null);
|
||||||
|
|
||||||
assertCounters(counters, 0, 100, 100);
|
counters.assertValues(0, 100, 100);
|
||||||
assertCachedData(cache, fakeDataSet);
|
assertCachedData(cache, fakeDataSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +227,8 @@ public final class CacheUtilTest {
|
|||||||
CacheUtil.cache(
|
CacheUtil.cache(
|
||||||
dataSpec, cache, /* cacheKeyFactory= */ null, dataSource, counters, /* isCanceled= */ null);
|
dataSpec, cache, /* cacheKeyFactory= */ null, dataSource, counters, /* isCanceled= */ null);
|
||||||
|
|
||||||
assertCounters(counters, 0, 20, 20);
|
counters.assertValues(0, 20, 20);
|
||||||
|
counters.reset();
|
||||||
|
|
||||||
CacheUtil.cache(
|
CacheUtil.cache(
|
||||||
new DataSpec(testUri),
|
new DataSpec(testUri),
|
||||||
@ -233,7 +238,7 @@ public final class CacheUtilTest {
|
|||||||
counters,
|
counters,
|
||||||
/* isCanceled= */ null);
|
/* isCanceled= */ null);
|
||||||
|
|
||||||
assertCounters(counters, 20, 80, 100);
|
counters.assertValues(20, 80, 100);
|
||||||
assertCachedData(cache, fakeDataSet);
|
assertCachedData(cache, fakeDataSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +254,7 @@ public final class CacheUtilTest {
|
|||||||
CacheUtil.cache(
|
CacheUtil.cache(
|
||||||
dataSpec, cache, /* cacheKeyFactory= */ null, dataSource, counters, /* isCanceled= */ null);
|
dataSpec, cache, /* cacheKeyFactory= */ null, dataSource, counters, /* isCanceled= */ null);
|
||||||
|
|
||||||
assertCounters(counters, 0, 100, 100);
|
counters.assertValues(0, 100, 100);
|
||||||
assertCachedData(cache, fakeDataSet);
|
assertCachedData(cache, fakeDataSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +271,8 @@ public final class CacheUtilTest {
|
|||||||
CacheUtil.cache(
|
CacheUtil.cache(
|
||||||
dataSpec, cache, /* cacheKeyFactory= */ null, dataSource, counters, /* isCanceled= */ null);
|
dataSpec, cache, /* cacheKeyFactory= */ null, dataSource, counters, /* isCanceled= */ null);
|
||||||
|
|
||||||
assertCounters(counters, 0, 20, 20);
|
counters.assertValues(0, 20, 20);
|
||||||
|
counters.reset();
|
||||||
|
|
||||||
CacheUtil.cache(
|
CacheUtil.cache(
|
||||||
new DataSpec(testUri),
|
new DataSpec(testUri),
|
||||||
@ -276,7 +282,7 @@ public final class CacheUtilTest {
|
|||||||
counters,
|
counters,
|
||||||
/* isCanceled= */ null);
|
/* isCanceled= */ null);
|
||||||
|
|
||||||
assertCounters(counters, 20, 80, 100);
|
counters.assertValues(20, 80, 100);
|
||||||
assertCachedData(cache, fakeDataSet);
|
assertCachedData(cache, fakeDataSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,7 +297,7 @@ public final class CacheUtilTest {
|
|||||||
CacheUtil.cache(
|
CacheUtil.cache(
|
||||||
dataSpec, cache, /* cacheKeyFactory= */ null, dataSource, counters, /* isCanceled= */ null);
|
dataSpec, cache, /* cacheKeyFactory= */ null, dataSource, counters, /* isCanceled= */ null);
|
||||||
|
|
||||||
assertCounters(counters, 0, 100, 1000);
|
counters.assertValues(0, 100, 1000);
|
||||||
assertCachedData(cache, fakeDataSet);
|
assertCachedData(cache, fakeDataSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,7 +318,7 @@ public final class CacheUtilTest {
|
|||||||
new byte[CacheUtil.DEFAULT_BUFFER_SIZE_BYTES],
|
new byte[CacheUtil.DEFAULT_BUFFER_SIZE_BYTES],
|
||||||
/* priorityTaskManager= */ null,
|
/* priorityTaskManager= */ null,
|
||||||
/* priority= */ 0,
|
/* priority= */ 0,
|
||||||
/* counters= */ null,
|
/* progressListener= */ null,
|
||||||
/* isCanceled= */ null,
|
/* isCanceled= */ null,
|
||||||
/* enableEOFException= */ true);
|
/* enableEOFException= */ true);
|
||||||
fail();
|
fail();
|
||||||
@ -328,9 +334,9 @@ public final class CacheUtilTest {
|
|||||||
new FakeDataSet()
|
new FakeDataSet()
|
||||||
.newData("test_data")
|
.newData("test_data")
|
||||||
.appendReadData(TestUtil.buildTestData(100))
|
.appendReadData(TestUtil.buildTestData(100))
|
||||||
.appendReadAction(() -> assertCounters(counters, 0, 100, 300))
|
.appendReadAction(() -> counters.assertValues(0, 100, 300))
|
||||||
.appendReadData(TestUtil.buildTestData(100))
|
.appendReadData(TestUtil.buildTestData(100))
|
||||||
.appendReadAction(() -> assertCounters(counters, 0, 200, 300))
|
.appendReadAction(() -> counters.assertValues(0, 200, 300))
|
||||||
.appendReadData(TestUtil.buildTestData(100))
|
.appendReadData(TestUtil.buildTestData(100))
|
||||||
.endData();
|
.endData();
|
||||||
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
|
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
|
||||||
@ -343,7 +349,7 @@ public final class CacheUtilTest {
|
|||||||
counters,
|
counters,
|
||||||
/* isCanceled= */ null);
|
/* isCanceled= */ null);
|
||||||
|
|
||||||
assertCounters(counters, 0, 300, 300);
|
counters.assertValues(0, 300, 300);
|
||||||
assertCachedData(cache, fakeDataSet);
|
assertCachedData(cache, fakeDataSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,7 +375,7 @@ public final class CacheUtilTest {
|
|||||||
new byte[CacheUtil.DEFAULT_BUFFER_SIZE_BYTES],
|
new byte[CacheUtil.DEFAULT_BUFFER_SIZE_BYTES],
|
||||||
/* priorityTaskManager= */ null,
|
/* priorityTaskManager= */ null,
|
||||||
/* priority= */ 0,
|
/* priority= */ 0,
|
||||||
/* counters= */ null,
|
/* progressListener= */ null,
|
||||||
/* isCanceled= */ null,
|
/* isCanceled= */ null,
|
||||||
true);
|
true);
|
||||||
CacheUtil.remove(dataSpec, cache, /* cacheKeyFactory= */ null);
|
CacheUtil.remove(dataSpec, cache, /* cacheKeyFactory= */ null);
|
||||||
@ -377,10 +383,34 @@ public final class CacheUtilTest {
|
|||||||
assertCacheEmpty(cache);
|
assertCacheEmpty(cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertCounters(CachingCounters counters, int alreadyCachedBytes,
|
private static final class CachingCounters implements CacheUtil.ProgressListener {
|
||||||
int newlyCachedBytes, int contentLength) {
|
|
||||||
assertThat(counters.alreadyCachedBytes).isEqualTo(alreadyCachedBytes);
|
private long contentLength = C.LENGTH_UNSET;
|
||||||
assertThat(counters.newlyCachedBytes).isEqualTo(newlyCachedBytes);
|
private long bytesAlreadyCached;
|
||||||
assertThat(counters.contentLength).isEqualTo(contentLength);
|
private long bytesNewlyCached;
|
||||||
|
private boolean seenFirstProgressUpdate;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgress(long contentLength, long bytesCached, long newBytesCached) {
|
||||||
|
this.contentLength = contentLength;
|
||||||
|
if (!seenFirstProgressUpdate) {
|
||||||
|
bytesAlreadyCached = bytesCached;
|
||||||
|
seenFirstProgressUpdate = true;
|
||||||
|
}
|
||||||
|
bytesNewlyCached = bytesCached - bytesAlreadyCached;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertValues(int bytesAlreadyCached, int bytesNewlyCached, int contentLength) {
|
||||||
|
assertThat(this.bytesAlreadyCached).isEqualTo(bytesAlreadyCached);
|
||||||
|
assertThat(this.bytesNewlyCached).isEqualTo(bytesNewlyCached);
|
||||||
|
assertThat(this.contentLength).isEqualTo(contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
contentLength = C.LENGTH_UNSET;
|
||||||
|
bytesAlreadyCached = 0;
|
||||||
|
bytesNewlyCached = 0;
|
||||||
|
seenFirstProgressUpdate = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ import java.util.List;
|
|||||||
* <p>Example usage:
|
* <p>Example usage:
|
||||||
*
|
*
|
||||||
* <pre>{@code
|
* <pre>{@code
|
||||||
* SimpleCache cache = new SimpleCache(downloadFolder, new NoOpCacheEvictor());
|
* SimpleCache cache = new SimpleCache(downloadFolder, new NoOpCacheEvictor(), databaseProvider);
|
||||||
* DefaultHttpDataSourceFactory factory = new DefaultHttpDataSourceFactory("ExoPlayer", null);
|
* DefaultHttpDataSourceFactory factory = new DefaultHttpDataSourceFactory("ExoPlayer", null);
|
||||||
* DownloaderConstructorHelper constructorHelper =
|
* DownloaderConstructorHelper constructorHelper =
|
||||||
* new DownloaderConstructorHelper(cache, factory);
|
* new DownloaderConstructorHelper(cache, factory);
|
||||||
@ -55,7 +55,7 @@ import java.util.List;
|
|||||||
* new DashDownloader(
|
* new DashDownloader(
|
||||||
* manifestUrl, Collections.singletonList(new StreamKey(0, 0, 0)), constructorHelper);
|
* manifestUrl, Collections.singletonList(new StreamKey(0, 0, 0)), constructorHelper);
|
||||||
* // Perform the download.
|
* // Perform the download.
|
||||||
* dashDownloader.download();
|
* dashDownloader.download(progressListener);
|
||||||
* // Access downloaded data using CacheDataSource
|
* // Access downloaded data using CacheDataSource
|
||||||
* CacheDataSource cacheDataSource =
|
* CacheDataSource cacheDataSource =
|
||||||
* new CacheDataSource(cache, factory.createDataSource(), CacheDataSource.FLAG_BLOCK_ON_CACHE);
|
* new CacheDataSource(cache, factory.createDataSource(), CacheDataSource.FLAG_BLOCK_ON_CACHE);
|
||||||
|
@ -62,6 +62,7 @@ public class DashDownloaderTest {
|
|||||||
|
|
||||||
private SimpleCache cache;
|
private SimpleCache cache;
|
||||||
private File tempFolder;
|
private File tempFolder;
|
||||||
|
private ProgressListener progressListener;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
@ -69,6 +70,7 @@ public class DashDownloaderTest {
|
|||||||
tempFolder =
|
tempFolder =
|
||||||
Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "ExoPlayerTest");
|
Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "ExoPlayerTest");
|
||||||
cache = new SimpleCache(tempFolder, new NoOpCacheEvictor());
|
cache = new SimpleCache(tempFolder, new NoOpCacheEvictor());
|
||||||
|
progressListener = new ProgressListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -77,7 +79,7 @@ public class DashDownloaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateWithDefaultDownloaderFactory() throws Exception {
|
public void testCreateWithDefaultDownloaderFactory() {
|
||||||
DownloaderConstructorHelper constructorHelper =
|
DownloaderConstructorHelper constructorHelper =
|
||||||
new DownloaderConstructorHelper(Mockito.mock(Cache.class), DummyDataSource.FACTORY);
|
new DownloaderConstructorHelper(Mockito.mock(Cache.class), DummyDataSource.FACTORY);
|
||||||
DownloaderFactory factory = new DefaultDownloaderFactory(constructorHelper);
|
DownloaderFactory factory = new DefaultDownloaderFactory(constructorHelper);
|
||||||
@ -105,7 +107,7 @@ public class DashDownloaderTest {
|
|||||||
.setRandomData("audio_segment_3", 6);
|
.setRandomData("audio_segment_3", 6);
|
||||||
|
|
||||||
DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new StreamKey(0, 0, 0));
|
DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new StreamKey(0, 0, 0));
|
||||||
dashDownloader.download();
|
dashDownloader.download(progressListener);
|
||||||
assertCachedData(cache, fakeDataSet);
|
assertCachedData(cache, fakeDataSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +126,7 @@ public class DashDownloaderTest {
|
|||||||
.setRandomData("audio_segment_3", 6);
|
.setRandomData("audio_segment_3", 6);
|
||||||
|
|
||||||
DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new StreamKey(0, 0, 0));
|
DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new StreamKey(0, 0, 0));
|
||||||
dashDownloader.download();
|
dashDownloader.download(progressListener);
|
||||||
assertCachedData(cache, fakeDataSet);
|
assertCachedData(cache, fakeDataSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +145,7 @@ public class DashDownloaderTest {
|
|||||||
|
|
||||||
DashDownloader dashDownloader =
|
DashDownloader dashDownloader =
|
||||||
getDashDownloader(fakeDataSet, new StreamKey(0, 0, 0), new StreamKey(0, 1, 0));
|
getDashDownloader(fakeDataSet, new StreamKey(0, 0, 0), new StreamKey(0, 1, 0));
|
||||||
dashDownloader.download();
|
dashDownloader.download(progressListener);
|
||||||
assertCachedData(cache, fakeDataSet);
|
assertCachedData(cache, fakeDataSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +166,7 @@ public class DashDownloaderTest {
|
|||||||
.setRandomData("period_2_segment_3", 3);
|
.setRandomData("period_2_segment_3", 3);
|
||||||
|
|
||||||
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
|
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
|
||||||
dashDownloader.download();
|
dashDownloader.download(progressListener);
|
||||||
assertCachedData(cache, fakeDataSet);
|
assertCachedData(cache, fakeDataSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +188,7 @@ public class DashDownloaderTest {
|
|||||||
|
|
||||||
DashDownloader dashDownloader =
|
DashDownloader dashDownloader =
|
||||||
getDashDownloader(factory, new StreamKey(0, 0, 0), new StreamKey(0, 1, 0));
|
getDashDownloader(factory, new StreamKey(0, 0, 0), new StreamKey(0, 1, 0));
|
||||||
dashDownloader.download();
|
dashDownloader.download(progressListener);
|
||||||
|
|
||||||
DataSpec[] openedDataSpecs = fakeDataSource.getAndClearOpenedDataSpecs();
|
DataSpec[] openedDataSpecs = fakeDataSource.getAndClearOpenedDataSpecs();
|
||||||
assertThat(openedDataSpecs.length).isEqualTo(8);
|
assertThat(openedDataSpecs.length).isEqualTo(8);
|
||||||
@ -218,7 +220,7 @@ public class DashDownloaderTest {
|
|||||||
|
|
||||||
DashDownloader dashDownloader =
|
DashDownloader dashDownloader =
|
||||||
getDashDownloader(factory, new StreamKey(0, 0, 0), new StreamKey(1, 0, 0));
|
getDashDownloader(factory, new StreamKey(0, 0, 0), new StreamKey(1, 0, 0));
|
||||||
dashDownloader.download();
|
dashDownloader.download(progressListener);
|
||||||
|
|
||||||
DataSpec[] openedDataSpecs = fakeDataSource.getAndClearOpenedDataSpecs();
|
DataSpec[] openedDataSpecs = fakeDataSource.getAndClearOpenedDataSpecs();
|
||||||
assertThat(openedDataSpecs.length).isEqualTo(8);
|
assertThat(openedDataSpecs.length).isEqualTo(8);
|
||||||
@ -248,12 +250,12 @@ public class DashDownloaderTest {
|
|||||||
|
|
||||||
DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new StreamKey(0, 0, 0));
|
DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new StreamKey(0, 0, 0));
|
||||||
try {
|
try {
|
||||||
dashDownloader.download();
|
dashDownloader.download(progressListener);
|
||||||
fail();
|
fail();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Expected.
|
// Expected.
|
||||||
}
|
}
|
||||||
dashDownloader.download();
|
dashDownloader.download(progressListener);
|
||||||
assertCachedData(cache, fakeDataSet);
|
assertCachedData(cache, fakeDataSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,18 +274,17 @@ public class DashDownloaderTest {
|
|||||||
.setRandomData("audio_segment_3", 6);
|
.setRandomData("audio_segment_3", 6);
|
||||||
|
|
||||||
DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new StreamKey(0, 0, 0));
|
DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new StreamKey(0, 0, 0));
|
||||||
assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(0);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
dashDownloader.download();
|
dashDownloader.download(progressListener);
|
||||||
fail();
|
fail();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Failure expected after downloading init data, segment 1 and 2 bytes in segment 2.
|
// Failure expected after downloading init data, segment 1 and 2 bytes in segment 2.
|
||||||
}
|
}
|
||||||
assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(10 + 4 + 2);
|
progressListener.assertBytesDownloaded(10 + 4 + 2);
|
||||||
|
|
||||||
dashDownloader.download();
|
dashDownloader.download(progressListener);
|
||||||
assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(10 + 4 + 5 + 6);
|
progressListener.assertBytesDownloaded(10 + 4 + 5 + 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -301,7 +302,7 @@ public class DashDownloaderTest {
|
|||||||
|
|
||||||
DashDownloader dashDownloader =
|
DashDownloader dashDownloader =
|
||||||
getDashDownloader(fakeDataSet, new StreamKey(0, 0, 0), new StreamKey(0, 1, 0));
|
getDashDownloader(fakeDataSet, new StreamKey(0, 0, 0), new StreamKey(0, 1, 0));
|
||||||
dashDownloader.download();
|
dashDownloader.download(progressListener);
|
||||||
dashDownloader.remove();
|
dashDownloader.remove();
|
||||||
assertCacheEmpty(cache);
|
assertCacheEmpty(cache);
|
||||||
}
|
}
|
||||||
@ -315,7 +316,7 @@ public class DashDownloaderTest {
|
|||||||
|
|
||||||
DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new StreamKey(0, 0, 0));
|
DashDownloader dashDownloader = getDashDownloader(fakeDataSet, new StreamKey(0, 0, 0));
|
||||||
try {
|
try {
|
||||||
dashDownloader.download();
|
dashDownloader.download(progressListener);
|
||||||
fail();
|
fail();
|
||||||
} catch (DownloadException e) {
|
} catch (DownloadException e) {
|
||||||
// Expected.
|
// Expected.
|
||||||
@ -339,4 +340,17 @@ public class DashDownloaderTest {
|
|||||||
return keysList;
|
return keysList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class ProgressListener implements Downloader.ProgressListener {
|
||||||
|
|
||||||
|
private long bytesDownloaded;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgress(long contentLength, long bytesDownloaded, float percentDownloaded) {
|
||||||
|
this.bytesDownloaded = bytesDownloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertBytesDownloaded(long bytesDownloaded) {
|
||||||
|
assertThat(this.bytesDownloaded).isEqualTo(bytesDownloaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ import java.util.List;
|
|||||||
* <p>Example usage:
|
* <p>Example usage:
|
||||||
*
|
*
|
||||||
* <pre>{@code
|
* <pre>{@code
|
||||||
* SimpleCache cache = new SimpleCache(downloadFolder, new NoOpCacheEvictor());
|
* SimpleCache cache = new SimpleCache(downloadFolder, new NoOpCacheEvictor(), databaseProvider);
|
||||||
* DefaultHttpDataSourceFactory factory = new DefaultHttpDataSourceFactory("ExoPlayer", null);
|
* DefaultHttpDataSourceFactory factory = new DefaultHttpDataSourceFactory("ExoPlayer", null);
|
||||||
* DownloaderConstructorHelper constructorHelper =
|
* DownloaderConstructorHelper constructorHelper =
|
||||||
* new DownloaderConstructorHelper(cache, factory);
|
* new DownloaderConstructorHelper(cache, factory);
|
||||||
@ -50,7 +50,7 @@ import java.util.List;
|
|||||||
* Collections.singletonList(new StreamKey(HlsMasterPlaylist.GROUP_INDEX_VARIANT, 0)),
|
* Collections.singletonList(new StreamKey(HlsMasterPlaylist.GROUP_INDEX_VARIANT, 0)),
|
||||||
* constructorHelper);
|
* constructorHelper);
|
||||||
* // Perform the download.
|
* // Perform the download.
|
||||||
* hlsDownloader.download();
|
* hlsDownloader.download(progressListener);
|
||||||
* // Access downloaded data using CacheDataSource
|
* // Access downloaded data using CacheDataSource
|
||||||
* CacheDataSource cacheDataSource =
|
* CacheDataSource cacheDataSource =
|
||||||
* new CacheDataSource(cache, factory.createDataSource(), CacheDataSource.FLAG_BLOCK_ON_CACHE);
|
* new CacheDataSource(cache, factory.createDataSource(), CacheDataSource.FLAG_BLOCK_ON_CACHE);
|
||||||
|
@ -67,6 +67,7 @@ public class HlsDownloaderTest {
|
|||||||
|
|
||||||
private SimpleCache cache;
|
private SimpleCache cache;
|
||||||
private File tempFolder;
|
private File tempFolder;
|
||||||
|
private ProgressListener progressListener;
|
||||||
private FakeDataSet fakeDataSet;
|
private FakeDataSet fakeDataSet;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@ -74,7 +75,7 @@ public class HlsDownloaderTest {
|
|||||||
tempFolder =
|
tempFolder =
|
||||||
Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "ExoPlayerTest");
|
Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "ExoPlayerTest");
|
||||||
cache = new SimpleCache(tempFolder, new NoOpCacheEvictor());
|
cache = new SimpleCache(tempFolder, new NoOpCacheEvictor());
|
||||||
|
progressListener = new ProgressListener();
|
||||||
fakeDataSet =
|
fakeDataSet =
|
||||||
new FakeDataSet()
|
new FakeDataSet()
|
||||||
.setData(MASTER_PLAYLIST_URI, MASTER_PLAYLIST_DATA)
|
.setData(MASTER_PLAYLIST_URI, MASTER_PLAYLIST_DATA)
|
||||||
@ -94,7 +95,7 @@ public class HlsDownloaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateWithDefaultDownloaderFactory() throws Exception {
|
public void testCreateWithDefaultDownloaderFactory() {
|
||||||
DownloaderConstructorHelper constructorHelper =
|
DownloaderConstructorHelper constructorHelper =
|
||||||
new DownloaderConstructorHelper(Mockito.mock(Cache.class), DummyDataSource.FACTORY);
|
new DownloaderConstructorHelper(Mockito.mock(Cache.class), DummyDataSource.FACTORY);
|
||||||
DownloaderFactory factory = new DefaultDownloaderFactory(constructorHelper);
|
DownloaderFactory factory = new DefaultDownloaderFactory(constructorHelper);
|
||||||
@ -115,17 +116,16 @@ public class HlsDownloaderTest {
|
|||||||
public void testCounterMethods() throws Exception {
|
public void testCounterMethods() throws Exception {
|
||||||
HlsDownloader downloader =
|
HlsDownloader downloader =
|
||||||
getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX));
|
getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX));
|
||||||
downloader.download();
|
downloader.download(progressListener);
|
||||||
|
|
||||||
assertThat(downloader.getDownloadedBytes())
|
progressListener.assertBytesDownloaded(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12);
|
||||||
.isEqualTo(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDownloadRepresentation() throws Exception {
|
public void testDownloadRepresentation() throws Exception {
|
||||||
HlsDownloader downloader =
|
HlsDownloader downloader =
|
||||||
getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX));
|
getHlsDownloader(MASTER_PLAYLIST_URI, getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX));
|
||||||
downloader.download();
|
downloader.download(progressListener);
|
||||||
|
|
||||||
assertCachedData(
|
assertCachedData(
|
||||||
cache,
|
cache,
|
||||||
@ -143,7 +143,7 @@ public class HlsDownloaderTest {
|
|||||||
getHlsDownloader(
|
getHlsDownloader(
|
||||||
MASTER_PLAYLIST_URI,
|
MASTER_PLAYLIST_URI,
|
||||||
getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX, MASTER_MEDIA_PLAYLIST_2_INDEX));
|
getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX, MASTER_MEDIA_PLAYLIST_2_INDEX));
|
||||||
downloader.download();
|
downloader.download(progressListener);
|
||||||
|
|
||||||
assertCachedData(cache, fakeDataSet);
|
assertCachedData(cache, fakeDataSet);
|
||||||
}
|
}
|
||||||
@ -162,7 +162,7 @@ public class HlsDownloaderTest {
|
|||||||
.setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence2.ts", 15);
|
.setRandomData(MEDIA_PLAYLIST_3_DIR + "fileSequence2.ts", 15);
|
||||||
|
|
||||||
HlsDownloader downloader = getHlsDownloader(MASTER_PLAYLIST_URI, getKeys());
|
HlsDownloader downloader = getHlsDownloader(MASTER_PLAYLIST_URI, getKeys());
|
||||||
downloader.download();
|
downloader.download(progressListener);
|
||||||
|
|
||||||
assertCachedData(cache, fakeDataSet);
|
assertCachedData(cache, fakeDataSet);
|
||||||
}
|
}
|
||||||
@ -173,7 +173,7 @@ public class HlsDownloaderTest {
|
|||||||
getHlsDownloader(
|
getHlsDownloader(
|
||||||
MASTER_PLAYLIST_URI,
|
MASTER_PLAYLIST_URI,
|
||||||
getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX, MASTER_MEDIA_PLAYLIST_2_INDEX));
|
getKeys(MASTER_MEDIA_PLAYLIST_1_INDEX, MASTER_MEDIA_PLAYLIST_2_INDEX));
|
||||||
downloader.download();
|
downloader.download(progressListener);
|
||||||
downloader.remove();
|
downloader.remove();
|
||||||
|
|
||||||
assertCacheEmpty(cache);
|
assertCacheEmpty(cache);
|
||||||
@ -182,7 +182,7 @@ public class HlsDownloaderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testDownloadMediaPlaylist() throws Exception {
|
public void testDownloadMediaPlaylist() throws Exception {
|
||||||
HlsDownloader downloader = getHlsDownloader(MEDIA_PLAYLIST_1_URI, getKeys());
|
HlsDownloader downloader = getHlsDownloader(MEDIA_PLAYLIST_1_URI, getKeys());
|
||||||
downloader.download();
|
downloader.download(progressListener);
|
||||||
|
|
||||||
assertCachedData(
|
assertCachedData(
|
||||||
cache,
|
cache,
|
||||||
@ -205,7 +205,7 @@ public class HlsDownloaderTest {
|
|||||||
.setRandomData("fileSequence2.ts", 12);
|
.setRandomData("fileSequence2.ts", 12);
|
||||||
|
|
||||||
HlsDownloader downloader = getHlsDownloader(ENC_MEDIA_PLAYLIST_URI, getKeys());
|
HlsDownloader downloader = getHlsDownloader(ENC_MEDIA_PLAYLIST_URI, getKeys());
|
||||||
downloader.download();
|
downloader.download(progressListener);
|
||||||
assertCachedData(cache, fakeDataSet);
|
assertCachedData(cache, fakeDataSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,4 +222,18 @@ public class HlsDownloaderTest {
|
|||||||
}
|
}
|
||||||
return streamKeys;
|
return streamKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class ProgressListener implements Downloader.ProgressListener {
|
||||||
|
|
||||||
|
private long bytesDownloaded;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgress(long contentLength, long bytesDownloaded, float percentDownloaded) {
|
||||||
|
this.bytesDownloaded = bytesDownloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertBytesDownloaded(long bytesDownloaded) {
|
||||||
|
assertThat(this.bytesDownloaded).isEqualTo(bytesDownloaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ import java.util.List;
|
|||||||
* <p>Example usage:
|
* <p>Example usage:
|
||||||
*
|
*
|
||||||
* <pre>{@code
|
* <pre>{@code
|
||||||
* SimpleCache cache = new SimpleCache(downloadFolder, new NoOpCacheEvictor());
|
* SimpleCache cache = new SimpleCache(downloadFolder, new NoOpCacheEvictor(), databaseProvider);
|
||||||
* DefaultHttpDataSourceFactory factory = new DefaultHttpDataSourceFactory("ExoPlayer", null);
|
* DefaultHttpDataSourceFactory factory = new DefaultHttpDataSourceFactory("ExoPlayer", null);
|
||||||
* DownloaderConstructorHelper constructorHelper =
|
* DownloaderConstructorHelper constructorHelper =
|
||||||
* new DownloaderConstructorHelper(cache, factory);
|
* new DownloaderConstructorHelper(cache, factory);
|
||||||
@ -48,7 +48,7 @@ import java.util.List;
|
|||||||
* Collections.singletonList(new StreamKey(0, 0)),
|
* Collections.singletonList(new StreamKey(0, 0)),
|
||||||
* constructorHelper);
|
* constructorHelper);
|
||||||
* // Perform the download.
|
* // Perform the download.
|
||||||
* ssDownloader.download();
|
* ssDownloader.download(progressListener);
|
||||||
* // Access downloaded data using CacheDataSource
|
* // Access downloaded data using CacheDataSource
|
||||||
* CacheDataSource cacheDataSource =
|
* CacheDataSource cacheDataSource =
|
||||||
* new CacheDataSource(cache, factory.createDataSource(), CacheDataSource.FLAG_BLOCK_ON_CACHE);
|
* new CacheDataSource(cache, factory.createDataSource(), CacheDataSource.FLAG_BLOCK_ON_CACHE);
|
||||||
|
@ -75,12 +75,12 @@ public final class DownloadNotificationHelper {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
haveDownloadTasks = true;
|
haveDownloadTasks = true;
|
||||||
float downloadPercentage = download.getDownloadPercentage();
|
float downloadPercentage = download.getPercentDownloaded();
|
||||||
if (downloadPercentage != C.PERCENTAGE_UNSET) {
|
if (downloadPercentage != C.PERCENTAGE_UNSET) {
|
||||||
allDownloadPercentagesUnknown = false;
|
allDownloadPercentagesUnknown = false;
|
||||||
totalPercentage += downloadPercentage;
|
totalPercentage += downloadPercentage;
|
||||||
}
|
}
|
||||||
haveDownloadedBytes |= download.getDownloadedBytes() > 0;
|
haveDownloadedBytes |= download.getBytesDownloaded() > 0;
|
||||||
downloadTaskCount++;
|
downloadTaskCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ public final class DashDownloadTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testDownload() throws Exception {
|
public void testDownload() throws Exception {
|
||||||
DashDownloader dashDownloader = downloadContent();
|
DashDownloader dashDownloader = downloadContent();
|
||||||
dashDownloader.download();
|
dashDownloader.download(/* progressListener= */ null);
|
||||||
|
|
||||||
testRunner
|
testRunner
|
||||||
.setStreamName("test_h264_fixed_download")
|
.setStreamName("test_h264_fixed_download")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user