Commit e26b8824 by olly Committed by Oliver Woodman

Add Downloader for progressive content

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=162348129
parent 0717c350
...@@ -42,6 +42,7 @@ public class CacheUtilTest extends InstrumentationTestCase { ...@@ -42,6 +42,7 @@ public class CacheUtilTest extends InstrumentationTestCase {
* create a proxy for it. * create a proxy for it.
*/ */
public abstract static class AbstractFakeCache implements Cache { public abstract static class AbstractFakeCache implements Cache {
// This array is set to alternating length of cached and not cached regions in tests: // 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>, // spansAndGaps = {<length of 1st cached region>, <length of 1st not cached region>,
// <length of 2nd cached region>, <length of 2nd not cached region>, ... } // <length of 2nd cached region>, <length of 2nd not cached region>, ... }
...@@ -50,7 +51,7 @@ public class CacheUtilTest extends InstrumentationTestCase { ...@@ -50,7 +51,7 @@ public class CacheUtilTest extends InstrumentationTestCase {
private long contentLength; private long contentLength;
private void init() { private void init() {
spansAndGaps = new int[]{}; spansAndGaps = new int[] {};
contentLength = C.LENGTH_UNSET; contentLength = C.LENGTH_UNSET;
} }
...@@ -116,46 +117,35 @@ public class CacheUtilTest extends InstrumentationTestCase { ...@@ -116,46 +117,35 @@ public class CacheUtilTest extends InstrumentationTestCase {
CacheUtil.getKey(new DataSpec(testUri, 0, C.LENGTH_UNSET, null))); 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 { public void testGetCachedNoData() throws Exception {
CachingCounters counters = CachingCounters counters = new CachingCounters();
CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, null); CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, counters);
assertCounters(counters, 0, 0, C.LENGTH_UNSET); assertCounters(counters, 0, 0, C.LENGTH_UNSET);
} }
public void testGetCachedDataUnknownLength() throws Exception { public void testGetCachedDataUnknownLength() throws Exception {
// 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 = CachingCounters counters = new CachingCounters();
CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, null); CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, counters);
assertCounters(counters, 100, 0, C.LENGTH_UNSET); assertCounters(counters, 100, 0, C.LENGTH_UNSET);
} }
public void testGetCachedNoDataKnownLength() throws Exception { public void testGetCachedNoDataKnownLength() throws Exception {
mockCache.contentLength = 1000; mockCache.contentLength = 1000;
CachingCounters counters = CachingCounters counters = new CachingCounters();
CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, null); CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, counters);
assertCounters(counters, 0, 0, 1000); assertCounters(counters, 0, 0, 1000);
} }
public void testGetCached() throws Exception { public void testGetCached() throws Exception {
mockCache.contentLength = 1000; mockCache.contentLength = 1000;
mockCache.spansAndGaps = new int[]{100, 100, 200}; mockCache.spansAndGaps = new int[] {100, 100, 200};
CachingCounters counters = CachingCounters counters = new CachingCounters();
CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, null); CacheUtil.getCached(new DataSpec(Uri.parse("test")), mockCache, counters);
assertCounters(counters, 300, 0, 1000); assertCounters(counters, 300, 0, 1000);
} }
...@@ -164,8 +154,8 @@ public class CacheUtilTest extends InstrumentationTestCase { ...@@ -164,8 +154,8 @@ public class CacheUtilTest extends InstrumentationTestCase {
FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100); FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100);
FakeDataSource dataSource = new FakeDataSource(fakeDataSet); FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
CachingCounters counters = CachingCounters counters = new CachingCounters();
CacheUtil.cache(new DataSpec(Uri.parse("test_data")), cache, dataSource, null); CacheUtil.cache(new DataSpec(Uri.parse("test_data")), cache, dataSource, counters);
assertCounters(counters, 0, 100, 100); assertCounters(counters, 0, 100, 100);
assertCachedData(cache, fakeDataSet); assertCachedData(cache, fakeDataSet);
...@@ -177,7 +167,8 @@ public class CacheUtilTest extends InstrumentationTestCase { ...@@ -177,7 +167,8 @@ public class CacheUtilTest extends InstrumentationTestCase {
Uri testUri = Uri.parse("test_data"); Uri testUri = Uri.parse("test_data");
DataSpec dataSpec = new DataSpec(testUri, 10, 20, null); 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); assertCounters(counters, 0, 20, 20);
...@@ -194,7 +185,8 @@ public class CacheUtilTest extends InstrumentationTestCase { ...@@ -194,7 +185,8 @@ public class CacheUtilTest extends InstrumentationTestCase {
FakeDataSource dataSource = new FakeDataSource(fakeDataSet); FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
DataSpec dataSpec = new DataSpec(Uri.parse("test_data")); 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); assertCounters(counters, 0, 100, 100);
assertCachedData(cache, fakeDataSet); assertCachedData(cache, fakeDataSet);
...@@ -208,7 +200,8 @@ public class CacheUtilTest extends InstrumentationTestCase { ...@@ -208,7 +200,8 @@ public class CacheUtilTest extends InstrumentationTestCase {
Uri testUri = Uri.parse("test_data"); Uri testUri = Uri.parse("test_data");
DataSpec dataSpec = new DataSpec(testUri, 10, 20, null); 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); assertCounters(counters, 0, 20, 20);
...@@ -224,7 +217,8 @@ public class CacheUtilTest extends InstrumentationTestCase { ...@@ -224,7 +217,8 @@ public class CacheUtilTest extends InstrumentationTestCase {
Uri testUri = Uri.parse("test_data"); Uri testUri = Uri.parse("test_data");
DataSpec dataSpec = new DataSpec(testUri, 0, 1000, null); 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); assertCounters(counters, 0, 100, 1000);
assertCachedData(cache, fakeDataSet); assertCachedData(cache, fakeDataSet);
...@@ -288,10 +282,10 @@ public class CacheUtilTest extends InstrumentationTestCase { ...@@ -288,10 +282,10 @@ public class CacheUtilTest extends InstrumentationTestCase {
} }
private static void assertCounters(CachingCounters counters, int alreadyCachedBytes, private static void assertCounters(CachingCounters counters, int alreadyCachedBytes,
int downloadedBytes, int totalBytes) { int newlyCachedBytes, int contentLength) {
assertEquals(alreadyCachedBytes, counters.alreadyCachedBytes); assertEquals(alreadyCachedBytes, counters.alreadyCachedBytes);
assertEquals(downloadedBytes, counters.downloadedBytes); assertEquals(newlyCachedBytes, counters.newlyCachedBytes);
assertEquals(totalBytes, counters.totalBytes); assertEquals(contentLength, counters.contentLength);
} }
} }
/* /*
* Copyright (C) 2017 The Android Open Source Project * Copyright (C) 2017 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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. * limitations under the License.
*/ */
package com.google.android.exoplayer2.upstream.cache; package com.google.android.exoplayer2.upstream.cache;
...@@ -32,17 +32,21 @@ import java.util.NavigableSet; ...@@ -32,17 +32,21 @@ import java.util.NavigableSet;
@SuppressWarnings({"NonAtomicVolatileUpdate", "NonAtomicOperationOnVolatileField"}) @SuppressWarnings({"NonAtomicVolatileUpdate", "NonAtomicOperationOnVolatileField"})
public final class CacheUtil { public final class CacheUtil {
/** Holds the counters used during caching. */ /** Counters used during caching. */
public static class CachingCounters { public static class CachingCounters {
/** Total number of already cached bytes. */ /** The number of bytes already in the cache. */
public volatile long alreadyCachedBytes; public volatile long alreadyCachedBytes;
/** Total number of downloaded bytes. */ /** The number of newly cached bytes. */
public volatile long downloadedBytes; 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 * Returns the sum of {@link #alreadyCachedBytes} and {@link #newlyCachedBytes}.
* the length of the missing bytes is unknown this is set to {@link C#LENGTH_UNSET}.
*/ */
public volatile long totalBytes = C.LENGTH_UNSET; public long totalCachedBytes() {
return alreadyCachedBytes + newlyCachedBytes;
}
} }
/** Default buffer size to be used while caching. */ /** Default buffer size to be used while caching. */
...@@ -68,40 +72,51 @@ public final class CacheUtil { ...@@ -68,40 +72,51 @@ public final class CacheUtil {
} }
/** /**
* Returns already cached and missing bytes in the {@code cache} for the data defined by {@code * Sets a {@link CachingCounters} to contain the number of bytes already downloaded and the
* dataSpec}. * 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 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 counters The counters to be set. If null a new {@link CachingCounters} is created and * @param counters The {@link CachingCounters} to update.
* used.
* @return The used {@link CachingCounters} instance.
*/ */
public static CachingCounters getCached(DataSpec dataSpec, Cache cache, public static void getCached(DataSpec dataSpec, Cache cache, CachingCounters counters) {
CachingCounters counters) { String key = getKey(dataSpec);
try { long start = dataSpec.absoluteStreamPosition;
return internalCache(dataSpec, cache, null, null, null, 0, counters, false); long left = dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : cache.getContentLength(key);
} catch (IOException | InterruptedException e) { counters.contentLength = left;
throw new IllegalStateException(e); 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 * Caches the data defined by {@code dataSpec}, skipping already cached data. Caching stops early
* early if end of input is reached. * if the end of the input is reached.
* *
* @param dataSpec Defines the data to be cached. * @param dataSpec Defines the data to be cached.
* @param cache A {@link Cache} to store the data. * @param cache A {@link Cache} to store the data.
* @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 The counters to be set during caching. If not null its values reset to * @param counters Counters to update during caching.
* zero before using. If null a new {@link CachingCounters} is created and used.
* @return The used {@link CachingCounters} instance.
* @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. * @throws InterruptedException If the thread was interrupted.
*/ */
public static CachingCounters cache(DataSpec dataSpec, Cache cache, public static void cache(DataSpec dataSpec, Cache cache, DataSource upstream,
DataSource upstream, CachingCounters counters) throws IOException, InterruptedException { CachingCounters counters) throws IOException, InterruptedException {
return cache(dataSpec, cache, new CacheDataSource(cache, upstream), cache(dataSpec, cache, new CacheDataSource(cache, upstream),
new byte[DEFAULT_BUFFER_SIZE_BYTES], null, 0, counters, false); new byte[DEFAULT_BUFFER_SIZE_BYTES], null, 0, counters, false);
} }
...@@ -116,91 +131,51 @@ public final class CacheUtil { ...@@ -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 * @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 The counters to be set during caching. If not null its values reset to * @param counters Counters to update during caching.
* 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 * @param enableEOFException Whether to throw an {@link EOFException} if end of input has been
* reached unexpectedly. * reached unexpectedly.
* @return The used {@link CachingCounters} instance.
* @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. * @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, byte[] buffer, PriorityTaskManager priorityTaskManager, int priority,
CachingCounters counters, boolean enableEOFException) CachingCounters counters, boolean enableEOFException)
throws IOException, InterruptedException { throws IOException, InterruptedException {
Assertions.checkNotNull(dataSource); Assertions.checkNotNull(dataSource);
Assertions.checkNotNull(buffer); Assertions.checkNotNull(buffer);
return internalCache(dataSpec, cache, dataSource, buffer, priorityTaskManager, priority,
counters, enableEOFException);
}
/** if (counters != null) {
* Caches the data defined by {@code dataSpec} while skipping already cached data. If {@code // Initialize the CachingCounter values.
* dataSource} or {@code buffer} is null performs a dry run. getCached(dataSpec, cache, counters);
*
* @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();
} else { } else {
counters.alreadyCachedBytes = 0; // Dummy CachingCounters. No need to initialize as they will not be visible to the caller.
counters.downloadedBytes = 0; 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) { while (left != 0) {
long blockLength = cache.getCachedBytes(key, start, long blockLength = cache.getCachedBytes(key, start,
left != C.LENGTH_UNSET ? left : Long.MAX_VALUE); left != C.LENGTH_UNSET ? left : Long.MAX_VALUE);
// Skip already cached data
if (blockLength > 0) { if (blockLength > 0) {
counters.alreadyCachedBytes += blockLength; // Skip already cached data.
} else { } else {
// There is a hole in the cache which is at least "-blockLength" long. // There is a hole in the cache which is at least "-blockLength" long.
blockLength = -blockLength; blockLength = -blockLength;
if (dataSource != null && buffer != null) { long read = readAndDiscard(dataSpec, start, blockLength, dataSource, buffer,
long read = readAndDiscard(dataSpec, start, blockLength, dataSource, buffer, priorityTaskManager, priority, counters);
priorityTaskManager, priority, counters); if (read < blockLength) {
if (read < blockLength) { // Reached to the end of the data.
// Reached to the end of the data. if (enableEOFException && left != C.LENGTH_UNSET) {
if (enableEOFException && left != C.LENGTH_UNSET) { throw new EOFException();
throw new EOFException();
}
break;
} }
} else if (blockLength == Long.MAX_VALUE) {
break; break;
} }
} }
start += blockLength; start += blockLength;
if (left != C.LENGTH_UNSET) { left -= left == C.LENGTH_UNSET ? 0 : blockLength;
left -= blockLength;
}
} }
return counters;
} }
/** /**
...@@ -215,7 +190,7 @@ public final class CacheUtil { ...@@ -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 * @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 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 * @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.
*/ */
...@@ -238,8 +213,8 @@ public final class CacheUtil { ...@@ -238,8 +213,8 @@ public final class CacheUtil {
C.LENGTH_UNSET, dataSpec.key, C.LENGTH_UNSET, dataSpec.key,
dataSpec.flags | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH); dataSpec.flags | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH);
long resolvedLength = dataSource.open(dataSpec); long resolvedLength = dataSource.open(dataSpec);
if (counters.totalBytes == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) { if (counters.contentLength == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) {
counters.totalBytes = dataSpec.absoluteStreamPosition + resolvedLength; counters.contentLength = dataSpec.absoluteStreamPosition + resolvedLength;
} }
long totalRead = 0; long totalRead = 0;
while (totalRead != length) { while (totalRead != length) {
...@@ -250,13 +225,13 @@ public final class CacheUtil { ...@@ -250,13 +225,13 @@ public final class CacheUtil {
length != C.LENGTH_UNSET ? (int) Math.min(buffer.length, length - totalRead) length != C.LENGTH_UNSET ? (int) Math.min(buffer.length, length - totalRead)
: buffer.length); : buffer.length);
if (read == C.RESULT_END_OF_INPUT) { if (read == C.RESULT_END_OF_INPUT) {
if (counters.totalBytes == C.LENGTH_UNSET) { if (counters.contentLength == C.LENGTH_UNSET) {
counters.totalBytes = dataSpec.absoluteStreamPosition + totalRead; counters.contentLength = dataSpec.absoluteStreamPosition + totalRead;
} }
break; break;
} }
totalRead += read; totalRead += read;
counters.downloadedBytes += read; counters.newlyCachedBytes += read;
} }
return totalRead; return totalRead;
} catch (PriorityTaskManager.PriorityTooLowException exception) { } catch (PriorityTaskManager.PriorityTooLowException exception) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment