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
This commit is contained in:
eguven 2016-10-17 08:13:33 -07:00 committed by Oliver Woodman
parent cecb1f5f76
commit 06fb29c939
5 changed files with 34 additions and 43 deletions

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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(

View File

@ -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

View File

@ -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 {