Cache data with unknown length by default

We currently default to not caching data if the content length
cannot be resolved once the DataSource has been open. The
reason for this is to avoid caching progressive live streams.
By doing this we were accidentally not caching in other places
where caching is possible, such as DASH/SS/HLS segments during
playback if the server doesn't include a Content-Length header.
Also HLS encryption key chunks, which were very unlikely to be
cached during playback because we explicitly set FLAG_ALLOW_GZIP
(which normally stops content length from resolving) without
setting FLAG_ALLOW_CACHING_UNKNOWN_LENGTH.

It seems like a good idea to flip the default at this point,
and explicitly disable caching in the one case where we want
that to happen.

PiperOrigin-RevId: 223994110
This commit is contained in:
olly 2018-12-04 17:45:50 +00:00 committed by Oliver Woodman
parent 976a21f139
commit 5bbe3ae7d6
9 changed files with 36 additions and 30 deletions

View File

@ -9,6 +9,9 @@
([#3314](https://github.com/google/ExoPlayer/issues/3314)).
* Do not retry failed loads whose error is `FileNotFoundException`.
* Prevent Cea608Decoder from generating Subtitles with null Cues list
* Caching: Cache data with unknown length by default. The previous flag to opt in
to this behavior (`DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH`) has been
replaced with an opt out flag (`DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN`).
### 2.9.2 ###

View File

@ -850,6 +850,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private DataSpec dataSpec;
private long length;
@SuppressWarnings("method.invocation.invalid")
public ExtractingLoadable(
Uri uri,
DataSource dataSource,
@ -864,7 +865,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
this.positionHolder = new PositionHolder();
this.pendingExtractorSeek = true;
this.length = C.LENGTH_UNSET;
dataSpec = new DataSpec(uri, positionHolder.position, C.LENGTH_UNSET, customCacheKey);
dataSpec = buildDataSpec(/* position= */ 0);
}
// Loadable implementation.
@ -881,7 +882,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
ExtractorInput input = null;
try {
long position = positionHolder.position;
dataSpec = new DataSpec(uri, position, C.LENGTH_UNSET, customCacheKey);
dataSpec = buildDataSpec(position);
length = dataSource.open(dataSpec);
if (length != C.LENGTH_UNSET) {
length += position;
@ -915,6 +916,17 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
// Internal methods.
private DataSpec buildDataSpec(long position) {
// Disable caching if the content length cannot be resolved, since this is indicative of a
// progressive live stream.
return new DataSpec(
uri,
position,
C.LENGTH_UNSET,
customCacheKey,
DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN);
}
private void setLoadPosition(long position, long timeUs) {
positionHolder.position = position;
seekTimeUs = timeUs;

View File

@ -287,8 +287,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
this.durationUs = durationUs;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream;
dataSpec =
new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH);
dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP);
timeline =
new SinglePeriodTimeline(durationUs, /* isSeekable= */ true, /* isDynamic= */ false, tag);
}

View File

@ -32,32 +32,29 @@ public final class DataSpec {
/**
* The flags that apply to any request for data. Possible flag values are {@link #FLAG_ALLOW_GZIP}
* and {@link #FLAG_ALLOW_CACHING_UNKNOWN_LENGTH}.
* and {@link #FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
value = {FLAG_ALLOW_GZIP, FLAG_ALLOW_CACHING_UNKNOWN_LENGTH})
value = {FLAG_ALLOW_GZIP, FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN})
public @interface Flags {}
/**
* Permits an underlying network stack to request that the server use gzip compression.
* <p>
* Should not typically be set if the data being requested is already compressed (e.g. most audio
* and video requests). May be set when requesting other data.
* <p>
* When a {@link DataSource} is used to request data with this flag set, and if the
* {@link DataSource} does make a network request, then the value returned from
* {@link DataSource#open(DataSpec)} will typically be {@link C#LENGTH_UNSET}. The data read from
* {@link DataSource#read(byte[], int, int)} will be the decompressed data.
* Allows an underlying network stack to request that the server use gzip compression.
*
* <p>Should not typically be set if the data being requested is already compressed (e.g. most
* audio and video requests). May be set when requesting other data.
*
* <p>When a {@link DataSource} is used to request data with this flag set, and if the {@link
* DataSource} does make a network request, then the value returned from {@link
* DataSource#open(DataSpec)} will typically be {@link C#LENGTH_UNSET}. The data read from {@link
* DataSource#read(byte[], int, int)} will be the decompressed data.
*/
public static final int FLAG_ALLOW_GZIP = 1;
/**
* Permits content to be cached even if its length can not be resolved. Typically this's the case
* for progressive live streams and when {@link #FLAG_ALLOW_GZIP} is used.
*/
public static final int FLAG_ALLOW_CACHING_UNKNOWN_LENGTH = 1 << 1; // 2
/** Prevents caching if the length cannot be resolved when the {@link DataSource} is opened. */
public static final int FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN = 1 << 1; // 2
/**
* The set of HTTP methods that are supported by ExoPlayer {@link HttpDataSource}s. One of {@link

View File

@ -91,11 +91,7 @@ public final class ParsingLoadable<T> implements Loadable {
* @param parser Parses the object from the response.
*/
public ParsingLoadable(DataSource dataSource, Uri uri, int type, Parser<? extends T> parser) {
this(
dataSource,
new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH),
type,
parser);
this(dataSource, new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP), type, parser);
}
/**

View File

@ -121,7 +121,7 @@ public final class CacheDataSink implements DataSink {
@Override
public void open(DataSpec dataSpec) throws CacheDataSinkException {
if (dataSpec.length == C.LENGTH_UNSET
&& !dataSpec.isFlagSet(DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH)) {
&& dataSpec.isFlagSet(DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN)) {
this.dataSpec = null;
return;
}

View File

@ -268,7 +268,7 @@ public final class CacheUtil {
dataSpec.position + absoluteStreamPosition - dataSpec.absoluteStreamPosition,
C.LENGTH_UNSET,
dataSpec.key,
dataSpec.flags | DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH);
dataSpec.flags);
long resolvedLength = dataSource.open(dataSpec);
if (counters.contentLength == C.LENGTH_UNSET && resolvedLength != C.LENGTH_UNSET) {
counters.contentLength = dataSpec.absoluteStreamPosition + resolvedLength;

View File

@ -602,7 +602,6 @@ public final class CacheDataSourceTest {
}
private DataSpec buildDataSpec(long position, long length, @Nullable String key) {
return new DataSpec(
testDataUri, position, length, key, DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH);
return new DataSpec(testDataUri, position, length, key);
}
}

View File

@ -83,7 +83,7 @@ public final class CacheAsserts {
* @throws IOException If an error occurred reading from the Cache.
*/
public static void assertDataCached(Cache cache, Uri uri, byte[] expected) throws IOException {
DataSpec dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_CACHING_UNKNOWN_LENGTH);
DataSpec dataSpec = new DataSpec(uri);
assertDataCached(cache, dataSpec, expected);
}