Add Downloader for progressive content

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=162348129
This commit is contained in:
olly 2017-07-18 07:08:49 -07:00 committed by Oliver Woodman
parent 0717c3502f
commit e26b8824dd
2 changed files with 101 additions and 132 deletions

View file

@ -42,6 +42,7 @@ public class CacheUtilTest extends InstrumentationTestCase {
* create a proxy for it.
*/
public abstract static class AbstractFakeCache implements Cache {
// This array is set to alternating length of cached and not cached regions in tests:
// spansAndGaps = {<length of 1st cached region>, <length of 1st not cached region>,
// <length of 2nd cached region>, <length of 2nd not cached region>, ... }
@ -50,7 +51,7 @@ public class CacheUtilTest extends InstrumentationTestCase {
private long contentLength;
private void init() {
spansAndGaps = new int[]{};
spansAndGaps = new int[] {};
contentLength = C.LENGTH_UNSET;
}
@ -116,46 +117,35 @@ public class CacheUtilTest extends InstrumentationTestCase {
CacheUtil.getKey(new DataSpec(testUri, 0, C.LENGTH_UNSET, null)));
}
public void testGetCachedCachingCounters() throws Exception {
DataSpec dataSpec = new DataSpec(Uri.parse("test"));
CachingCounters counters = CacheUtil.getCached(dataSpec, mockCache, null);
// getCached should create a CachingCounters and return it
assertNotNull(counters);
CachingCounters newCounters = CacheUtil.getCached(dataSpec, mockCache, counters);
// getCached should set and return given CachingCounters
assertEquals(counters, newCounters);
}
public void testGetCachedNoData() throws Exception {
CachingCounters counters =
CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, null);
CachingCounters counters = new CachingCounters();
CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, counters);
assertCounters(counters, 0, 0, C.LENGTH_UNSET);
}
public void testGetCachedDataUnknownLength() throws Exception {
// Mock there is 100 bytes cached at the beginning
mockCache.spansAndGaps = new int[]{100};
CachingCounters counters =
CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, null);
mockCache.spansAndGaps = new int[] {100};
CachingCounters counters = new CachingCounters();
CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, counters);
assertCounters(counters, 100, 0, C.LENGTH_UNSET);
}
public void testGetCachedNoDataKnownLength() throws Exception {
mockCache.contentLength = 1000;
CachingCounters counters =
CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, null);
CachingCounters counters = new CachingCounters();
CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, counters);
assertCounters(counters, 0, 0, 1000);
}
public void testGetCached() throws Exception {
mockCache.contentLength = 1000;
mockCache.spansAndGaps = new int[]{100, 100, 200};
CachingCounters counters =
CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, null);
mockCache.spansAndGaps = new int[] {100, 100, 200};
CachingCounters counters = new CachingCounters();
CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, counters);
assertCounters(counters, 300, 0, 1000);
}
@ -164,8 +154,8 @@ public class CacheUtilTest extends InstrumentationTestCase {
FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100);
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
CachingCounters counters =
CacheUtil.cache(new DataSpec(Uri.parse("test_data")), cache, dataSource, null);
CachingCounters counters = new CachingCounters();
CacheUtil.cache(new DataSpec(Uri.parse("test_data")), cache, dataSource, counters);
assertCounters(counters, 0, 100, 100);
assertCachedData(cache, fakeDataSet);
@ -177,7 +167,8 @@ public class CacheUtilTest extends InstrumentationTestCase {
Uri testUri = Uri.parse("test_data");
DataSpec dataSpec = new DataSpec(testUri, 10, 20, null);
CachingCounters counters = CacheUtil.cache(dataSpec, cache, dataSource, null);
CachingCounters counters = new CachingCounters();
CacheUtil.cache(dataSpec, cache, dataSource, counters);
assertCounters(counters, 0, 20, 20);
@ -194,7 +185,8 @@ public class CacheUtilTest extends InstrumentationTestCase {
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
DataSpec dataSpec = new DataSpec(Uri.parse("test_data"));
CachingCounters counters = CacheUtil.cache(dataSpec, cache, dataSource, null);
CachingCounters counters = new CachingCounters();
CacheUtil.cache(dataSpec, cache, dataSource, counters);
assertCounters(counters, 0, 100, 100);
assertCachedData(cache, fakeDataSet);
@ -208,7 +200,8 @@ public class CacheUtilTest extends InstrumentationTestCase {
Uri testUri = Uri.parse("test_data");
DataSpec dataSpec = new DataSpec(testUri, 10, 20, null);
CachingCounters counters = CacheUtil.cache(dataSpec, cache, dataSource, null);
CachingCounters counters = new CachingCounters();
CacheUtil.cache(dataSpec, cache, dataSource, counters);
assertCounters(counters, 0, 20, 20);
@ -224,7 +217,8 @@ public class CacheUtilTest extends InstrumentationTestCase {
Uri testUri = Uri.parse("test_data");
DataSpec dataSpec = new DataSpec(testUri, 0, 1000, null);
CachingCounters counters = CacheUtil.cache(dataSpec, cache, dataSource, null);
CachingCounters counters = new CachingCounters();
CacheUtil.cache(dataSpec, cache, dataSource, counters);
assertCounters(counters, 0, 100, 1000);
assertCachedData(cache, fakeDataSet);
@ -288,10 +282,10 @@ public class CacheUtilTest extends InstrumentationTestCase {
}
private static void assertCounters(CachingCounters counters, int alreadyCachedBytes,
int downloadedBytes, int totalBytes) {
int newlyCachedBytes, int contentLength) {
assertEquals(alreadyCachedBytes, counters.alreadyCachedBytes);
assertEquals(downloadedBytes, counters.downloadedBytes);
assertEquals(totalBytes, counters.totalBytes);
assertEquals(newlyCachedBytes, counters.newlyCachedBytes);
assertEquals(contentLength, counters.contentLength);
}
}

View file

@ -1,16 +1,16 @@
/*
* Copyright (C) 2017 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,
* Copyright (C) 2017 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
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.upstream.cache;
@ -32,17 +32,21 @@ import java.util.NavigableSet;
@SuppressWarnings({"NonAtomicVolatileUpdate", "NonAtomicOperationOnVolatileField"})
public final class CacheUtil {
/** Holds the counters used during caching. */
/** Counters used during caching. */
public static class CachingCounters {
/** Total number of already cached bytes. */
/** The number of bytes already in the cache. */
public volatile long alreadyCachedBytes;
/** Total number of downloaded bytes. */
public volatile long downloadedBytes;
/** 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;
/**
* Total number of bytes. This is the sum of already cached, downloaded and missing bytes. If
* the length of the missing bytes is unknown this is set to {@link C#LENGTH_UNSET}.
* Returns the sum of {@link #alreadyCachedBytes} and {@link #newlyCachedBytes}.
*/
public volatile long totalBytes = C.LENGTH_UNSET;
public long totalCachedBytes() {
return alreadyCachedBytes + newlyCachedBytes;
}
}
/** Default buffer size to be used while caching. */
@ -68,40 +72,51 @@ public final class CacheUtil {
}
/**
* Returns already cached and missing bytes in the {@code cache} for the data defined by {@code
* dataSpec}.
* Sets a {@link CachingCounters} to contain the number of bytes already downloaded and the
* length for the content defined by a {@code dataSpec}. {@link CachingCounters#newlyCachedBytes}
* is reset to 0.
*
* @param dataSpec Defines the data to be checked.
* @param cache A {@link Cache} which has the data.
* @param counters The counters to be set. If null a new {@link CachingCounters} is created and
* used.
* @return The used {@link CachingCounters} instance.
* @param counters The {@link CachingCounters} to update.
*/
public static CachingCounters getCached(DataSpec dataSpec, Cache cache,
CachingCounters counters) {
try {
return internalCache(dataSpec, cache, null, null, null, 0, counters, false);
} catch (IOException | InterruptedException e) {
throw new IllegalStateException(e);
public static void getCached(DataSpec dataSpec, Cache cache, CachingCounters counters) {
String key = getKey(dataSpec);
long start = dataSpec.absoluteStreamPosition;
long left = dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : cache.getContentLength(key);
counters.contentLength = left;
counters.alreadyCachedBytes = 0;
counters.newlyCachedBytes = 0;
while (left != 0) {
long blockLength = cache.getCachedBytes(key, start,
left != C.LENGTH_UNSET ? left : Long.MAX_VALUE);
if (blockLength > 0) {
counters.alreadyCachedBytes += blockLength;
} else {
blockLength = -blockLength;
if (blockLength == Long.MAX_VALUE) {
return;
}
}
start += blockLength;
left -= left == C.LENGTH_UNSET ? 0 : blockLength;
}
}
/**
* Caches the data defined by {@code dataSpec} while skipping already cached data. Caching stops
* early if end of input is reached.
* Caches the data defined by {@code dataSpec}, skipping already cached data. Caching stops early
* if the end of the input is reached.
*
* @param dataSpec Defines the data to be cached.
* @param cache A {@link Cache} to store the data.
* @param upstream A {@link DataSource} for reading data not in the cache.
* @param counters The counters to be set during caching. If not null its values reset to
* zero before using. If null a new {@link CachingCounters} is created and used.
* @return The used {@link CachingCounters} instance.
* @param counters Counters to update during caching.
* @throws IOException If an error occurs reading from the source.
* @throws InterruptedException If the thread was interrupted.
*/
public static CachingCounters cache(DataSpec dataSpec, Cache cache,
DataSource upstream, CachingCounters counters) throws IOException, InterruptedException {
return cache(dataSpec, cache, new CacheDataSource(cache, upstream),
public static void cache(DataSpec dataSpec, Cache cache, DataSource upstream,
CachingCounters counters) throws IOException, InterruptedException {
cache(dataSpec, cache, new CacheDataSource(cache, upstream),
new byte[DEFAULT_BUFFER_SIZE_BYTES], null, 0, counters, false);
}
@ -116,91 +131,51 @@ public final class CacheUtil {
* @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with
* caching.
* @param priority The priority of this task. Used with {@code priorityTaskManager}.
* @param counters The counters to be set during caching. If not null its values reset to
* zero before using. If null a new {@link CachingCounters} is created and used.
* @param counters Counters to update during caching.
* @param enableEOFException Whether to throw an {@link EOFException} if end of input has been
* reached unexpectedly.
* @return The used {@link CachingCounters} instance.
* @throws IOException If an error occurs reading from the source.
* @throws InterruptedException If the thread was interrupted.
*/
public static CachingCounters cache(DataSpec dataSpec, Cache cache, CacheDataSource dataSource,
public static void cache(DataSpec dataSpec, Cache cache, CacheDataSource dataSource,
byte[] buffer, PriorityTaskManager priorityTaskManager, int priority,
CachingCounters counters, boolean enableEOFException)
throws IOException, InterruptedException {
Assertions.checkNotNull(dataSource);
Assertions.checkNotNull(buffer);
return internalCache(dataSpec, cache, dataSource, buffer, priorityTaskManager, priority,
counters, enableEOFException);
}
/**
* Caches the data defined by {@code dataSpec} while skipping already cached data. If {@code
* dataSource} or {@code buffer} is null performs a dry run.
*
* @param dataSpec Defines the data to be cached.
* @param cache A {@link Cache} to store the data.
* @param dataSource A {@link CacheDataSource} that works on the {@code cache}. If null a dry run
* is performed.
* @param buffer The buffer to be used while caching. If null a dry run is performed.
* @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with
* caching.
* @param priority The priority of this task. Used with {@code priorityTaskManager}.
* @param counters The counters to be set during caching. If not null its values reset to
* zero before using. If null a new {@link CachingCounters} is created and used.
* @param enableEOFException Whether to throw an {@link EOFException} if end of input has been
* reached unexpectedly.
* @return The used {@link CachingCounters} instance.
* @throws IOException If not dry run and an error occurs reading from the source.
* @throws InterruptedException If not dry run and the thread was interrupted.
*/
private static CachingCounters internalCache(DataSpec dataSpec, Cache cache,
CacheDataSource dataSource, byte[] buffer, PriorityTaskManager priorityTaskManager,
int priority, CachingCounters counters, boolean enableEOFException)
throws IOException, InterruptedException {
long start = dataSpec.absoluteStreamPosition;
long left = dataSpec.length;
String key = getKey(dataSpec);
if (left == C.LENGTH_UNSET) {
left = cache.getContentLength(key);
}
if (counters == null) {
counters = new CachingCounters();
if (counters != null) {
// Initialize the CachingCounter values.
getCached(dataSpec, cache, counters);
} else {
counters.alreadyCachedBytes = 0;
counters.downloadedBytes = 0;
// Dummy CachingCounters. No need to initialize as they will not be visible to the caller.
counters = new CachingCounters();
}
counters.totalBytes = left;
String key = getKey(dataSpec);
long start = dataSpec.absoluteStreamPosition;
long left = dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : cache.getContentLength(key);
while (left != 0) {
long blockLength = cache.getCachedBytes(key, start,
left != C.LENGTH_UNSET ? left : Long.MAX_VALUE);
// Skip already cached data
if (blockLength > 0) {
counters.alreadyCachedBytes += blockLength;
// Skip already cached data.
} else {
// There is a hole in the cache which is at least "-blockLength" long.
blockLength = -blockLength;
if (dataSource != null && buffer != null) {
long read = readAndDiscard(dataSpec, start, blockLength, dataSource, buffer,
priorityTaskManager, priority, counters);
if (read < blockLength) {
// Reached to the end of the data.
if (enableEOFException && left != C.LENGTH_UNSET) {
throw new EOFException();
}
break;
long read = readAndDiscard(dataSpec, start, blockLength, dataSource, buffer,
priorityTaskManager, priority, counters);
if (read < blockLength) {
// Reached to the end of the data.
if (enableEOFException && left != C.LENGTH_UNSET) {
throw new EOFException();
}
} else if (blockLength == Long.MAX_VALUE) {
break;
}
}
start += blockLength;
if (left != C.LENGTH_UNSET) {
left -= blockLength;
}
left -= left == C.LENGTH_UNSET ? 0 : blockLength;
}
return counters;
}
/**
@ -215,7 +190,7 @@ public final class CacheUtil {
* @param priorityTaskManager If not null it's used to check whether it is allowed to proceed with
* caching.
* @param priority The priority of this task.
* @param counters The counters to be set during reading.
* @param counters Counters to be set during reading.
* @return Number of read bytes, or 0 if no data is available because the end of the opened range
* has been reached.
*/
@ -238,8 +213,8 @@ public final class CacheUtil {
C.LENGTH_UNSET, dataSpec.key,
dataSpec.flags | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH);
long resolvedLength = dataSource.open(dataSpec);
if (counters.totalBytes == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) {
counters.totalBytes = dataSpec.absoluteStreamPosition + resolvedLength;
if (counters.contentLength == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) {
counters.contentLength = dataSpec.absoluteStreamPosition + resolvedLength;
}
long totalRead = 0;
while (totalRead != length) {
@ -250,13 +225,13 @@ public final class CacheUtil {
length != C.LENGTH_UNSET ? (int) Math.min(buffer.length, length - totalRead)
: buffer.length);
if (read == C.RESULT_END_OF_INPUT) {
if (counters.totalBytes == C.LENGTH_UNSET) {
counters.totalBytes = dataSpec.absoluteStreamPosition + totalRead;
if (counters.contentLength == C.LENGTH_UNSET) {
counters.contentLength = dataSpec.absoluteStreamPosition + totalRead;
}
break;
}
totalRead += read;
counters.downloadedBytes += read;
counters.newlyCachedBytes += read;
}
return totalRead;
} catch (PriorityTaskManager.PriorityTooLowException exception) {