diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java index 001c6adc87..93d7a123bc 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/SimpleCacheTest.java @@ -192,6 +192,41 @@ public class SimpleCacheTest extends InstrumentationTestCase { assertEquals(0, cacheDir.listFiles().length); } + + public void testGetCachedBytes() throws Exception { + SimpleCache simpleCache = getSimpleCache(); + CacheSpan cacheSpan = simpleCache.startReadWrite(KEY_1, 0); + + // No cached bytes, returns -'length' + assertEquals(-100, simpleCache.getCachedBytes(KEY_1, 0, 100)); + + // Position value doesn't affect the return value + assertEquals(-100, simpleCache.getCachedBytes(KEY_1, 20, 100)); + + addCache(simpleCache, KEY_1, 0, 15); + + // Returns the length of a single span + assertEquals(15, simpleCache.getCachedBytes(KEY_1, 0, 100)); + + // Value is capped by the 'length' + assertEquals(10, simpleCache.getCachedBytes(KEY_1, 0, 10)); + + addCache(simpleCache, KEY_1, 15, 35); + + // Returns the length of two adjacent spans + assertEquals(50, simpleCache.getCachedBytes(KEY_1, 0, 100)); + + addCache(simpleCache, KEY_1, 60, 10); + + // Not adjacent span doesn't affect return value + assertEquals(50, simpleCache.getCachedBytes(KEY_1, 0, 100)); + + // Returns length of hole up to the next cached span + assertEquals(-5, simpleCache.getCachedBytes(KEY_1, 55, 100)); + + simpleCache.releaseHoleSpan(cacheSpan); + } + private SimpleCache getSimpleCache() { return new SimpleCache(cacheDir, new NoOpCacheEvictor()); } diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java b/library/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java index 8dcfe75670..86ff810142 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/cache/Cache.java @@ -198,6 +198,18 @@ public interface Cache { */ boolean isCached(String key, long position, long length); + /** + * Returns the length of the cached data block starting from the {@code position} to the block end + * up to {@code length} bytes. If the {@code position} isn't cached then -(the length of the gap + * to the next cached data up to {@code length} bytes) is returned. + * + * @param key The cache key for the data. + * @param position The starting position of the data. + * @param length The maximum length of the data to be returned. + * @return the length of the cached or not cached data block length. + */ + long getCachedBytes(String key, long position, long length); + /** * Sets the content length for the given key. * diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java b/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java index c744a176ad..fb59d23666 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java @@ -106,43 +106,49 @@ import java.util.TreeSet; * which defines the maximum extents of the hole in the cache. */ public SimpleCacheSpan getSpan(long position) { - SimpleCacheSpan span = getSpanInternal(position); - if (!span.isCached) { - SimpleCacheSpan ceilEntry = cachedSpans.ceiling(span); - return ceilEntry == null ? SimpleCacheSpan.createOpenHole(key, position) - : SimpleCacheSpan.createClosedHole(key, position, ceilEntry.position - position); + SimpleCacheSpan lookupSpan = SimpleCacheSpan.createLookup(key, position); + SimpleCacheSpan floorSpan = cachedSpans.floor(lookupSpan); + if (floorSpan != null && floorSpan.position + floorSpan.length > position) { + return floorSpan; } - return span; + SimpleCacheSpan ceilSpan = cachedSpans.ceiling(lookupSpan); + return ceilSpan == null ? SimpleCacheSpan.createOpenHole(key, position) + : SimpleCacheSpan.createClosedHole(key, position, ceilSpan.position - position); } - /** Queries if a range is entirely available in the cache. */ - public boolean isCached(long position, long length) { - SimpleCacheSpan floorSpan = getSpanInternal(position); - if (!floorSpan.isCached) { + /** + * Returns the length of the cached data block starting from the {@code position} to the block end + * up to {@code length} bytes. If the {@code position} isn't cached then -(the length of the gap + * to the next cached data up to {@code length} bytes) is returned. + * + * @param position The starting position of the data. + * @param length The maximum length of the data to be returned. + * @return the length of the cached or not cached data block length. + */ + public long getCachedBytes(long position, long length) { + SimpleCacheSpan span = getSpan(position); + if (span.isHoleSpan()) { // We don't have a span covering the start of the queried region. - return false; + return -Math.min(span.isOpenEnded() ? Long.MAX_VALUE : span.length, length); } long queryEndPosition = position + length; - long currentEndPosition = floorSpan.position + floorSpan.length; - if (currentEndPosition >= queryEndPosition) { - // floorSpan covers the queried region. - return true; - } - for (SimpleCacheSpan next : cachedSpans.tailSet(floorSpan, false)) { - if (next.position > currentEndPosition) { - // There's a hole in the cache within the queried region. - return false; - } - // We expect currentEndPosition to always equal (next.position + next.length), but - // perform a max check anyway to guard against the existence of overlapping spans. - currentEndPosition = Math.max(currentEndPosition, next.position + next.length); - if (currentEndPosition >= queryEndPosition) { - // We've found spans covering the queried region. - return true; + long currentEndPosition = span.position + span.length; + if (currentEndPosition < queryEndPosition) { + for (SimpleCacheSpan next : cachedSpans.tailSet(span, false)) { + if (next.position > currentEndPosition) { + // There's a hole in the cache within the queried region. + break; + } + // We expect currentEndPosition to always equal (next.position + next.length), but + // perform a max check anyway to guard against the existence of overlapping spans. + currentEndPosition = Math.max(currentEndPosition, next.position + next.length); + if (currentEndPosition >= queryEndPosition) { + // We've found spans covering the queried region. + break; + } } } - // We ran out of spans before covering the queried region. - return false; + return Math.min(currentEndPosition - position, length); } /** @@ -190,15 +196,4 @@ import java.util.TreeSet; return result; } - /** - * Returns the span containing the position. If there isn't one, it returns the lookup span it - * used for searching. - */ - private SimpleCacheSpan getSpanInternal(long position) { - SimpleCacheSpan lookupSpan = SimpleCacheSpan.createLookup(key, position); - SimpleCacheSpan floorSpan = cachedSpans.floor(lookupSpan); - return floorSpan == null || floorSpan.position + floorSpan.length <= position ? lookupSpan - : floorSpan; - } - } diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java b/library/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java index e3e887c6ed..14f006c850 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/cache/SimpleCache.java @@ -354,7 +354,13 @@ public final class SimpleCache implements Cache { @Override public synchronized boolean isCached(String key, long position, long length) { CachedContent cachedContent = index.get(key); - return cachedContent != null && cachedContent.isCached(position, length); + return cachedContent != null && cachedContent.getCachedBytes(position, length) >= length; + } + + @Override + public synchronized long getCachedBytes(String key, long position, long length) { + CachedContent cachedContent = index.get(key); + return cachedContent != null ? cachedContent.getCachedBytes(position, length) : -length; } @Override