mirror of
https://github.com/samsonjs/media.git
synced 2026-04-06 11:25:46 +00:00
Add Downloader for progressive content
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=162348129
This commit is contained in:
parent
0717c3502f
commit
e26b8824dd
2 changed files with 101 additions and 132 deletions
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue