Modify CacheUtil.cache() for polling counters
Getting active status of caching is needed to display on UI. Instead of a listener interface polling was chosen because of simplicity and better suits to UI refreshing. CachingCounters.downloadedBytes was updated after whole data is downloaded. Now it's updated for each read into buffer. Buffer length defines how finer these updates are. CachingCounters.totalBytes is added so UI can display a progress bar. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=160142048
This commit is contained in:
parent
a7ed199622
commit
3fbfe29d27
297
library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java
vendored
Normal file
297
library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java
vendored
Normal file
@ -0,0 +1,297 @@
|
||||
/*
|
||||
* 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
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.upstream.cache;
|
||||
|
||||
import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCacheEmpty;
|
||||
import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedData;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.testutil.FakeDataSource;
|
||||
import com.google.android.exoplayer2.testutil.FakeDataSource.FakeDataSet;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.upstream.cache.CacheUtil.CachingCounters;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.Mock;
|
||||
|
||||
/**
|
||||
* Tests {@link CacheUtil}.
|
||||
*/
|
||||
public class CacheUtilTest extends InstrumentationTestCase {
|
||||
|
||||
/**
|
||||
* Abstract fake Cache implementation used by the test. This class must be public so Mockito can
|
||||
* 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>, ... }
|
||||
// Ideally it should end with a cached region but it shouldn't matter for any code.
|
||||
private int[] spansAndGaps;
|
||||
private long contentLength;
|
||||
|
||||
private void init() {
|
||||
spansAndGaps = new int[]{};
|
||||
contentLength = C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCachedBytes(String key, long position, long length) {
|
||||
for (int i = 0; i < spansAndGaps.length; i++) {
|
||||
int spanOrGap = spansAndGaps[i];
|
||||
if (position < spanOrGap) {
|
||||
long left = Math.min(spanOrGap - position, length);
|
||||
return (i & 1) == 1 ? -left : left;
|
||||
}
|
||||
position -= spanOrGap;
|
||||
}
|
||||
return -length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLength(String key) {
|
||||
return contentLength;
|
||||
}
|
||||
}
|
||||
|
||||
@Mock(answer = Answers.CALLS_REAL_METHODS) private AbstractFakeCache mockCache;
|
||||
private File tempFolder;
|
||||
private SimpleCache cache;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
TestUtil.setUpMockito(this);
|
||||
mockCache.init();
|
||||
tempFolder = Util.createTempDirectory(getInstrumentation().getContext(), "ExoPlayerTest");
|
||||
cache = new SimpleCache(tempFolder, new NoOpCacheEvictor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
Util.recursiveDelete(tempFolder);
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void testGenerateKey() throws Exception {
|
||||
assertNotNull(CacheUtil.generateKey(Uri.EMPTY));
|
||||
|
||||
Uri testUri = Uri.parse("test");
|
||||
String key = CacheUtil.generateKey(testUri);
|
||||
assertNotNull(key);
|
||||
|
||||
// Should generate the same key for the same input
|
||||
assertEquals(key, CacheUtil.generateKey(testUri));
|
||||
|
||||
// Should generate different key for different input
|
||||
assertFalse(key.equals(CacheUtil.generateKey(Uri.parse("test2"))));
|
||||
}
|
||||
|
||||
public void testGetKey() throws Exception {
|
||||
Uri testUri = Uri.parse("test");
|
||||
String key = "key";
|
||||
// If DataSpec.key is present, returns it
|
||||
assertEquals(key, CacheUtil.getKey(new DataSpec(testUri, 0, C.LENGTH_UNSET, key)));
|
||||
// If not generates a new one using DataSpec.uri
|
||||
assertEquals(CacheUtil.generateKey(testUri),
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
assertCounters(counters, 300, 0, 1000);
|
||||
}
|
||||
|
||||
public void testCache() throws Exception {
|
||||
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);
|
||||
|
||||
assertCounters(counters, 0, 100, 100);
|
||||
assertCachedData(cache, fakeDataSet);
|
||||
}
|
||||
|
||||
public void testCacheSetOffsetAndLength() throws Exception {
|
||||
FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100);
|
||||
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
|
||||
|
||||
Uri testUri = Uri.parse("test_data");
|
||||
DataSpec dataSpec = new DataSpec(testUri, 10, 20, null);
|
||||
CachingCounters counters = CacheUtil.cache(dataSpec, cache, dataSource, null);
|
||||
|
||||
assertCounters(counters, 0, 20, 20);
|
||||
|
||||
CacheUtil.cache(new DataSpec(testUri), cache, dataSource, counters);
|
||||
|
||||
assertCounters(counters, 20, 80, 100);
|
||||
assertCachedData(cache, fakeDataSet);
|
||||
}
|
||||
|
||||
public void testCacheUnknownLength() throws Exception {
|
||||
FakeDataSet fakeDataSet = new FakeDataSet().newData("test_data")
|
||||
.setSimulateUnknownLength(true)
|
||||
.appendReadData(TestUtil.buildTestData(100)).endData();
|
||||
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
|
||||
|
||||
DataSpec dataSpec = new DataSpec(Uri.parse("test_data"));
|
||||
CachingCounters counters = CacheUtil.cache(dataSpec, cache, dataSource, null);
|
||||
|
||||
assertCounters(counters, 0, 100, 100);
|
||||
assertCachedData(cache, fakeDataSet);
|
||||
}
|
||||
|
||||
public void testCacheUnknownLengthPartialCaching() throws Exception {
|
||||
FakeDataSet fakeDataSet = new FakeDataSet().newData("test_data")
|
||||
.setSimulateUnknownLength(true)
|
||||
.appendReadData(TestUtil.buildTestData(100)).endData();
|
||||
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
|
||||
|
||||
Uri testUri = Uri.parse("test_data");
|
||||
DataSpec dataSpec = new DataSpec(testUri, 10, 20, null);
|
||||
CachingCounters counters = CacheUtil.cache(dataSpec, cache, dataSource, null);
|
||||
|
||||
assertCounters(counters, 0, 20, 20);
|
||||
|
||||
CacheUtil.cache(new DataSpec(testUri), cache, dataSource, counters);
|
||||
|
||||
assertCounters(counters, 20, 80, 100);
|
||||
assertCachedData(cache, fakeDataSet);
|
||||
}
|
||||
|
||||
public void testCacheLengthExceedsActualDataLength() throws Exception {
|
||||
FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100);
|
||||
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
|
||||
|
||||
Uri testUri = Uri.parse("test_data");
|
||||
DataSpec dataSpec = new DataSpec(testUri, 0, 1000, null);
|
||||
CachingCounters counters = CacheUtil.cache(dataSpec, cache, dataSource, null);
|
||||
|
||||
assertCounters(counters, 0, 100, 1000);
|
||||
assertCachedData(cache, fakeDataSet);
|
||||
}
|
||||
|
||||
public void testCacheThrowEOFException() throws Exception {
|
||||
FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100);
|
||||
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
|
||||
|
||||
Uri testUri = Uri.parse("test_data");
|
||||
DataSpec dataSpec = new DataSpec(testUri, 0, 1000, null);
|
||||
|
||||
try {
|
||||
CacheUtil.cache(dataSpec, cache, new CacheDataSource(cache, dataSource),
|
||||
new byte[CacheUtil.DEFAULT_BUFFER_SIZE_BYTES], null, 0, null,
|
||||
/*enableEOFException*/ true);
|
||||
fail();
|
||||
} catch (EOFException e) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
public void testCachePolling() throws Exception {
|
||||
final CachingCounters counters = new CachingCounters();
|
||||
FakeDataSet fakeDataSet = new FakeDataSet().newData("test_data")
|
||||
.appendReadData(TestUtil.buildTestData(100))
|
||||
.appendReadAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
assertCounters(counters, 0, 100, 300);
|
||||
}
|
||||
})
|
||||
.appendReadData(TestUtil.buildTestData(100))
|
||||
.appendReadAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
assertCounters(counters, 0, 200, 300);
|
||||
}
|
||||
})
|
||||
.appendReadData(TestUtil.buildTestData(100)).endData();
|
||||
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
|
||||
|
||||
CacheUtil.cache(new DataSpec(Uri.parse("test_data")), cache, dataSource, counters);
|
||||
|
||||
assertCounters(counters, 0, 300, 300);
|
||||
assertCachedData(cache, fakeDataSet);
|
||||
}
|
||||
|
||||
public void testRemove() throws Exception {
|
||||
FakeDataSet fakeDataSet = new FakeDataSet().setRandomData("test_data", 100);
|
||||
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
|
||||
|
||||
Uri uri = Uri.parse("test_data");
|
||||
CacheUtil.cache(new DataSpec(uri), cache,
|
||||
// set maxCacheFileSize to 10 to make sure there are multiple spans
|
||||
new CacheDataSource(cache, dataSource, 0, 10),
|
||||
new byte[CacheUtil.DEFAULT_BUFFER_SIZE_BYTES], null, 0, null, true);
|
||||
CacheUtil.remove(cache, CacheUtil.generateKey(uri));
|
||||
|
||||
assertCacheEmpty(cache);
|
||||
}
|
||||
|
||||
private static void assertCounters(CachingCounters counters, int alreadyCachedBytes,
|
||||
int downloadedBytes, int totalBytes) {
|
||||
assertEquals(alreadyCachedBytes, counters.alreadyCachedBytes);
|
||||
assertEquals(downloadedBytes, counters.downloadedBytes);
|
||||
assertEquals(totalBytes, counters.totalBytes);
|
||||
}
|
||||
|
||||
}
|
@ -22,28 +22,32 @@ import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.PriorityTaskManager;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.util.NavigableSet;
|
||||
|
||||
/**
|
||||
* Caching related utility methods.
|
||||
*/
|
||||
@SuppressWarnings({"NonAtomicVolatileUpdate", "NonAtomicOperationOnVolatileField"})
|
||||
public final class CacheUtil {
|
||||
|
||||
/** Holds the counters used during caching. */
|
||||
public static class CachingCounters {
|
||||
/** Total number of already cached bytes. */
|
||||
public long alreadyCachedBytes;
|
||||
public volatile long alreadyCachedBytes;
|
||||
/** Total number of downloaded bytes. */
|
||||
public volatile long downloadedBytes;
|
||||
/**
|
||||
* Total number of downloaded bytes.
|
||||
*
|
||||
* <p>{@link #getCached(DataSpec, Cache, CachingCounters)} sets it to the count of the missing
|
||||
* bytes or to {@link C#LENGTH_UNSET} if {@code dataSpec} is unbounded and content length isn't
|
||||
* available in the {@code cache}.
|
||||
* 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}.
|
||||
*/
|
||||
public long downloadedBytes;
|
||||
public volatile long totalBytes = C.LENGTH_UNSET;
|
||||
}
|
||||
|
||||
/** Default buffer size to be used while caching. */
|
||||
public static final int DEFAULT_BUFFER_SIZE_BYTES = 128 * 1024;
|
||||
|
||||
/**
|
||||
* Generates a cache key out of the given {@link Uri}.
|
||||
*
|
||||
@ -76,14 +80,34 @@ public final class CacheUtil {
|
||||
public static CachingCounters getCached(DataSpec dataSpec, Cache cache,
|
||||
CachingCounters counters) {
|
||||
try {
|
||||
return internalCache(dataSpec, cache, null, null, null, 0, counters);
|
||||
return internalCache(dataSpec, cache, null, null, null, 0, counters, false);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches the data defined by {@code dataSpec} while skipping already cached data.
|
||||
* Caches the data defined by {@code dataSpec} while skipping already cached data. Caching stops
|
||||
* early if end of 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.
|
||||
* @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),
|
||||
new byte[DEFAULT_BUFFER_SIZE_BYTES], null, 0, counters, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches the data defined by {@code dataSpec} while skipping already cached data. Caching stops
|
||||
* early if end of input is reached and {@code enableEOFException} is false.
|
||||
*
|
||||
* @param dataSpec Defines the data to be cached.
|
||||
* @param cache A {@link Cache} to store the data.
|
||||
@ -94,17 +118,20 @@ public final class CacheUtil {
|
||||
* @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 an error occurs reading from the source.
|
||||
* @throws InterruptedException If the thread was interrupted.
|
||||
*/
|
||||
public static CachingCounters cache(DataSpec dataSpec, Cache cache, CacheDataSource dataSource,
|
||||
byte[] buffer, PriorityTaskManager priorityTaskManager, int priority,
|
||||
CachingCounters counters) throws IOException, InterruptedException {
|
||||
CachingCounters counters, boolean enableEOFException)
|
||||
throws IOException, InterruptedException {
|
||||
Assertions.checkNotNull(dataSource);
|
||||
Assertions.checkNotNull(buffer);
|
||||
return internalCache(dataSpec, cache, dataSource, buffer, priorityTaskManager, priority,
|
||||
counters);
|
||||
counters, enableEOFException);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,21 +148,21 @@ public final class CacheUtil {
|
||||
* @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) throws IOException, InterruptedException {
|
||||
long start = dataSpec.position;
|
||||
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 (left == C.LENGTH_UNSET) {
|
||||
left = Long.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
if (counters == null) {
|
||||
counters = new CachingCounters();
|
||||
@ -143,8 +170,11 @@ public final class CacheUtil {
|
||||
counters.alreadyCachedBytes = 0;
|
||||
counters.downloadedBytes = 0;
|
||||
}
|
||||
while (left > 0) {
|
||||
long blockLength = cache.getCachedBytes(key, start, left);
|
||||
counters.totalBytes = left;
|
||||
|
||||
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;
|
||||
@ -152,24 +182,21 @@ public final class CacheUtil {
|
||||
// There is a hole in the cache which is at least "-blockLength" long.
|
||||
blockLength = -blockLength;
|
||||
if (dataSource != null && buffer != null) {
|
||||
DataSpec subDataSpec = new DataSpec(dataSpec.uri, start,
|
||||
blockLength == Long.MAX_VALUE ? C.LENGTH_UNSET : blockLength, key);
|
||||
long read = readAndDiscard(subDataSpec, dataSource, buffer, priorityTaskManager,
|
||||
priority);
|
||||
counters.downloadedBytes += read;
|
||||
long read = readAndDiscard(dataSpec, start, blockLength, dataSource, buffer,
|
||||
priorityTaskManager, priority, counters);
|
||||
if (read < blockLength) {
|
||||
// Reached end of data.
|
||||
// Reached to the end of the data.
|
||||
if (enableEOFException && left != C.LENGTH_UNSET) {
|
||||
throw new EOFException();
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (blockLength == Long.MAX_VALUE) {
|
||||
counters.downloadedBytes = C.LENGTH_UNSET;
|
||||
break;
|
||||
} else {
|
||||
counters.downloadedBytes += blockLength;
|
||||
}
|
||||
}
|
||||
start += blockLength;
|
||||
if (left != Long.MAX_VALUE) {
|
||||
if (left != C.LENGTH_UNSET) {
|
||||
left -= blockLength;
|
||||
}
|
||||
}
|
||||
@ -179,36 +206,56 @@ public final class CacheUtil {
|
||||
/**
|
||||
* Reads and discards all data specified by the {@code dataSpec}.
|
||||
*
|
||||
* @param dataSpec Defines the data to be read.
|
||||
* @param dataSpec Defines the data to be read. {@code absoluteStreamPosition} and {@code length}
|
||||
* fields are overwritten by the following parameters.
|
||||
* @param absoluteStreamPosition The absolute position of the data to be read.
|
||||
* @param length Length of the data to be read, or {@link C#LENGTH_UNSET} if it is unknown.
|
||||
* @param dataSource The {@link DataSource} to read the data from.
|
||||
* @param buffer The buffer to be used while downloading.
|
||||
* @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.
|
||||
* @return Number of read bytes, or 0 if no data is available because the end of the opened range
|
||||
* has been reached.
|
||||
*/
|
||||
private static long readAndDiscard(DataSpec dataSpec, DataSource dataSource, byte[] buffer,
|
||||
PriorityTaskManager priorityTaskManager, int priority)
|
||||
throws IOException, InterruptedException {
|
||||
private static long readAndDiscard(DataSpec dataSpec, long absoluteStreamPosition, long length,
|
||||
DataSource dataSource, byte[] buffer, PriorityTaskManager priorityTaskManager, int priority,
|
||||
CachingCounters counters) throws IOException, InterruptedException {
|
||||
while (true) {
|
||||
if (priorityTaskManager != null) {
|
||||
// Wait for any other thread with higher priority to finish its job.
|
||||
priorityTaskManager.proceed(priority);
|
||||
}
|
||||
try {
|
||||
dataSource.open(dataSpec);
|
||||
// Create a new dataSpec setting length to C.LENGTH_UNSET to prevent getting an error in
|
||||
// case the given length exceeds the end of input.
|
||||
dataSpec = new DataSpec(dataSpec.uri, dataSpec.postBody, absoluteStreamPosition,
|
||||
dataSpec.position + absoluteStreamPosition - dataSpec.absoluteStreamPosition,
|
||||
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;
|
||||
}
|
||||
long totalRead = 0;
|
||||
while (true) {
|
||||
while (totalRead != length) {
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
int read = dataSource.read(buffer, 0, buffer.length);
|
||||
int read = dataSource.read(buffer, 0,
|
||||
length != C.LENGTH_UNSET ? (int) Math.min(buffer.length, length - totalRead)
|
||||
: buffer.length);
|
||||
if (read == C.RESULT_END_OF_INPUT) {
|
||||
return totalRead;
|
||||
if (counters.totalBytes == C.LENGTH_UNSET) {
|
||||
counters.totalBytes = dataSpec.absoluteStreamPosition + totalRead;
|
||||
}
|
||||
break;
|
||||
}
|
||||
totalRead += read;
|
||||
counters.downloadedBytes += read;
|
||||
}
|
||||
return totalRead;
|
||||
} catch (PriorityTaskManager.PriorityTooLowException exception) {
|
||||
// catch and try again
|
||||
} finally {
|
||||
|
Loading…
x
Reference in New Issue
Block a user