From 06fb29c939e74f7198a12bcece7b668df05ee52f Mon Sep 17 00:00:00 2001 From: eguven Date: Mon, 17 Oct 2016 08:13:33 -0700 Subject: [PATCH] Support caching of multi segment DASH, HLS and Smooth Streaming. Use sha1 of content uri if a custom cache key or content id isn't provided. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=136351830 --- .../upstream/cache/CacheDataSourceTest.java | 3 +- .../source/ExtractorMediaPeriod.java | 4 +- .../source/dash/DashMediaSource.java | 6 +-- .../source/dash/manifest/Representation.java | 38 +++++++++++-------- .../upstream/cache/CacheDataSource.java | 26 ++++--------- 5 files changed, 34 insertions(+), 43 deletions(-) diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java index 5e85ad4d4c..d46458db2b 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java @@ -177,8 +177,7 @@ public class CacheDataSourceTest extends InstrumentationTestCase { builder.setSimulateUnknownLength(simulateUnknownLength); builder.appendReadData(TEST_DATA); FakeDataSource upstream = builder.build(); - return new CacheDataSource(simpleCache, upstream, - CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_CACHE_UNBOUNDED_REQUESTS, + return new CacheDataSource(simpleCache, upstream, CacheDataSource.FLAG_BLOCK_ON_CACHE, MAX_CACHE_FILE_SIZE); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index 27bd1f677f..18de1b9df9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -39,7 +39,6 @@ import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader.Loadable; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ConditionVariable; -import com.google.android.exoplayer2.util.Util; import java.io.EOFException; import java.io.IOException; @@ -593,8 +592,7 @@ import java.io.IOException; ExtractorInput input = null; try { long position = positionHolder.position; - length = dataSource.open( - new DataSpec(uri, position, C.LENGTH_UNSET, Util.sha1(uri.toString()))); + length = dataSource.open(new DataSpec(uri, position, C.LENGTH_UNSET, null)); if (length != C.LENGTH_UNSET) { length += position; } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index 766f1e0ebf..f22d1693a9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -124,7 +124,7 @@ public final class DashMediaSource implements MediaSource { this.minLoadableRetryCount = minLoadableRetryCount; this.livePresentationDelayMs = livePresentationDelayMs; eventDispatcher = new EventDispatcher(eventHandler, eventListener); - manifestParser = new DashManifestParser(generateContentId()); + manifestParser = new DashManifestParser(); manifestCallback = new ManifestCallback(); manifestUriLock = new Object(); periodsById = new SparseArray<>(); @@ -468,10 +468,6 @@ public final class DashMediaSource implements MediaSource { } } - private String generateContentId() { - return Util.sha1(manifestUri.toString()); - } - private static final class PeriodSeekInfo { public static PeriodSeekInfo createPeriodSeekInfo( diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java index 9c6d2e1582..6ebd69e29b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/Representation.java @@ -57,7 +57,6 @@ public abstract class Representation { */ public final long presentationTimeOffsetUs; - private final String cacheKey; private final RangedUri initializationUri; /** @@ -81,7 +80,8 @@ public abstract class Representation { * @param revisionId Identifies the revision of the content. * @param format The format of the representation. * @param segmentBase A segment base element for the representation. - * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. + * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. This + * parameter is ignored if {@code segmentBase} consists of multiple segments. * @return The constructed instance. */ public static Representation newInstance(String contentId, long revisionId, Format format, @@ -91,7 +91,7 @@ public abstract class Representation { (SingleSegmentBase) segmentBase, customCacheKey, C.LENGTH_UNSET); } else if (segmentBase instanceof MultiSegmentBase) { return new MultiSegmentRepresentation(contentId, revisionId, format, - (MultiSegmentBase) segmentBase, customCacheKey); + (MultiSegmentBase) segmentBase); } else { throw new IllegalArgumentException("segmentBase must be of type SingleSegmentBase or " + "MultiSegmentBase"); @@ -99,12 +99,10 @@ public abstract class Representation { } private Representation(String contentId, long revisionId, Format format, - SegmentBase segmentBase, String customCacheKey) { + SegmentBase segmentBase) { this.contentId = contentId; this.revisionId = revisionId; this.format = format; - this.cacheKey = customCacheKey != null ? customCacheKey - : contentId + "." + format.id + "." + revisionId; initializationUri = segmentBase.getInitialization(this); presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs(); } @@ -129,12 +127,10 @@ public abstract class Representation { public abstract DashSegmentIndex getIndex(); /** - * Returns a cache key for the representation, in the format - * {@code contentId + "." + format.id + "." + revisionId}. + * Returns a cache key for the representation if a custom cache key or content id has been + * provided and there is only single segment. */ - public String getCacheKey() { - return cacheKey; - } + public abstract String getCacheKey(); /** * A DASH representation consisting of a single segment. @@ -151,6 +147,7 @@ public abstract class Representation { */ public final long contentLength; + private final String cacheKey; private final RangedUri indexUri; private final SingleSegmentIndex segmentIndex; @@ -187,9 +184,11 @@ public abstract class Representation { */ public SingleSegmentRepresentation(String contentId, long revisionId, Format format, SingleSegmentBase segmentBase, String customCacheKey, long contentLength) { - super(contentId, revisionId, format, segmentBase, customCacheKey); + super(contentId, revisionId, format, segmentBase); this.uri = Uri.parse(segmentBase.uri); this.indexUri = segmentBase.getIndex(); + this.cacheKey = customCacheKey != null ? customCacheKey + : contentId != null ? contentId + "." + format.id + "." + revisionId : null; this.contentLength = contentLength; // If we have an index uri then the index is defined externally, and we shouldn't return one // directly. If we don't, then we can't do better than an index defining a single segment. @@ -207,6 +206,11 @@ public abstract class Representation { return segmentIndex; } + @Override + public String getCacheKey() { + return cacheKey; + } + } /** @@ -222,11 +226,10 @@ public abstract class Representation { * @param revisionId Identifies the revision of the content. * @param format The format of the representation. * @param segmentBase The segment base underlying the representation. - * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. */ public MultiSegmentRepresentation(String contentId, long revisionId, Format format, - MultiSegmentBase segmentBase, String customCacheKey) { - super(contentId, revisionId, format, segmentBase, customCacheKey); + MultiSegmentBase segmentBase) { + super(contentId, revisionId, format, segmentBase); this.segmentBase = segmentBase; } @@ -240,6 +243,11 @@ public abstract class Representation { return this; } + @Override + public String getCacheKey() { + return null; + } + // DashSegmentIndex implementation. @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index 727eb068ce..1f56d4ef83 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -26,6 +26,7 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.FileDataSource; import com.google.android.exoplayer2.upstream.TeeDataSource; import com.google.android.exoplayer2.upstream.cache.CacheDataSink.CacheDataSinkException; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.io.InterruptedIOException; import java.lang.annotation.Retention; @@ -50,8 +51,7 @@ public final class CacheDataSource implements DataSource { * Flags controlling the cache's behavior. */ @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = {FLAG_BLOCK_ON_CACHE, FLAG_IGNORE_CACHE_ON_ERROR, - FLAG_CACHE_UNBOUNDED_REQUESTS}) + @IntDef(flag = true, value = {FLAG_BLOCK_ON_CACHE, FLAG_IGNORE_CACHE_ON_ERROR}) public @interface Flags {} /** * A flag indicating whether we will block reads if the cache key is locked. If this flag is @@ -66,13 +66,6 @@ public final class CacheDataSource implements DataSource { */ public static final int FLAG_IGNORE_CACHE_ON_ERROR = 1 << 1; - /** - * A flag indicating whether the response is cached if the range of the request is unbounded. - * Disabled by default because, as a side effect, this may allow streams with every chunk from a - * separate URL cached which is broken currently. - */ - public static final int FLAG_CACHE_UNBOUNDED_REQUESTS = 1 << 2; - /** * Listener of {@link CacheDataSource} events. */ @@ -98,7 +91,6 @@ public final class CacheDataSource implements DataSource { private final boolean blockOnCache; private final boolean ignoreCacheOnError; - private final boolean bypassUnboundedRequests; private DataSource currentDataSource; private boolean currentRequestUnbounded; @@ -127,8 +119,8 @@ public final class CacheDataSource implements DataSource { * * @param cache The cache. * @param upstream A {@link DataSource} for reading data not in the cache. - * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} - * and {@link #FLAG_CACHE_UNBOUNDED_REQUESTS} or 0. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link + * #FLAG_IGNORE_CACHE_ON_ERROR} or 0. * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the cached data size * exceeds this value, then the data will be fragmented into multiple cache files. The * finer-grained this is the finer-grained the eviction policy can be. @@ -148,8 +140,8 @@ public final class CacheDataSource implements DataSource { * @param upstream A {@link DataSource} for reading data not in the cache. * @param cacheReadDataSource A {@link DataSource} for reading data from the cache. * @param cacheWriteDataSink A {@link DataSink} for writing data to the cache. - * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR} - * and {@link #FLAG_CACHE_UNBOUNDED_REQUESTS} or 0. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link + * #FLAG_IGNORE_CACHE_ON_ERROR} or 0. * @param eventListener An optional {@link EventListener} to receive events. */ public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource, @@ -158,7 +150,6 @@ public final class CacheDataSource implements DataSource { this.cacheReadDataSource = cacheReadDataSource; this.blockOnCache = (flags & FLAG_BLOCK_ON_CACHE) != 0; this.ignoreCacheOnError = (flags & FLAG_IGNORE_CACHE_ON_ERROR) != 0; - this.bypassUnboundedRequests = (flags & FLAG_CACHE_UNBOUNDED_REQUESTS) == 0; this.upstreamDataSource = upstream; if (cacheWriteDataSink != null) { this.cacheWriteDataSource = new TeeDataSource(upstream, cacheWriteDataSink); @@ -173,10 +164,9 @@ public final class CacheDataSource implements DataSource { try { uri = dataSpec.uri; flags = dataSpec.flags; - key = dataSpec.key; + key = dataSpec.key != null ? dataSpec.key : Util.sha1(uri.toString()); readPosition = dataSpec.position; - currentRequestIgnoresCache = (ignoreCacheOnError && seenCacheError) - || (bypassUnboundedRequests && dataSpec.length == C.LENGTH_UNSET); + currentRequestIgnoresCache = ignoreCacheOnError && seenCacheError; if (dataSpec.length != C.LENGTH_UNSET || currentRequestIgnoresCache) { bytesRemaining = dataSpec.length; } else {