diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java index f973852452..df9975d43b 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheUtilTest.java @@ -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 = {, , // , , ... } @@ -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); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java index 9a83d6f3be..cf2dedbe54 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheUtil.java @@ -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) {