Commit 0c6566bc by Oliver Woodman

Add flags to DataSpec. Support GZIP option.

Also remove uriIsFullStream. It's not doing anything particularly
useful, so I think it makes sense to remove it from the public API;
it's unlikely anyone is using it.

Issue: #329
parent 3868b1d4
...@@ -438,13 +438,14 @@ public class HlsChunkSource { ...@@ -438,13 +438,14 @@ public class HlsChunkSource {
private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex) { private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex) {
Uri mediaPlaylistUri = Util.getMergedUri(baseUri, enabledVariants[variantIndex].url); Uri mediaPlaylistUri = Util.getMergedUri(baseUri, enabledVariants[variantIndex].url);
DataSpec dataSpec = new DataSpec(mediaPlaylistUri, 0, C.LENGTH_UNBOUNDED, null); DataSpec dataSpec = new DataSpec(mediaPlaylistUri, 0, C.LENGTH_UNBOUNDED, null,
DataSpec.FLAG_ALLOW_GZIP);
return new MediaPlaylistChunk(variantIndex, upstreamDataSource, dataSpec, return new MediaPlaylistChunk(variantIndex, upstreamDataSource, dataSpec,
mediaPlaylistUri.toString()); mediaPlaylistUri.toString());
} }
private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv) { private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv) {
DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNBOUNDED, null); DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNBOUNDED, null, DataSpec.FLAG_ALLOW_GZIP);
return new EncryptionKeyChunk(upstreamDataSource, dataSpec, iv); return new EncryptionKeyChunk(upstreamDataSource, dataSpec, iv);
} }
......
...@@ -230,7 +230,7 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream ...@@ -230,7 +230,7 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream
long remainingLength = resolvedLength != C.LENGTH_UNBOUNDED long remainingLength = resolvedLength != C.LENGTH_UNBOUNDED
? resolvedLength - loadPosition : C.LENGTH_UNBOUNDED; ? resolvedLength - loadPosition : C.LENGTH_UNBOUNDED;
loadDataSpec = new DataSpec(dataSpec.uri, dataSpec.position + loadPosition, loadDataSpec = new DataSpec(dataSpec.uri, dataSpec.position + loadPosition,
remainingLength, dataSpec.key); remainingLength, dataSpec.key, dataSpec.flags);
dataSource.open(loadDataSpec); dataSource.open(loadDataSpec);
} }
......
...@@ -26,21 +26,31 @@ import android.net.Uri; ...@@ -26,21 +26,31 @@ import android.net.Uri;
public final class DataSpec { public final class DataSpec {
/** /**
* Identifies the source from which data should be read. * 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_UNBOUNDED}. The data read
* from {@link DataSource#read(byte[], int, int)} will be the decompressed data.
*/ */
public final Uri uri; public static final int FLAG_ALLOW_GZIP = 1;
/** /**
* True if the data at {@link #uri} is the full stream. False otherwise. An example where this * Identifies the source from which data should be read.
* may be false is if {@link #uri} defines the location of a cached part of the stream.
*/ */
public final boolean uriIsFullStream; public final Uri uri;
/** /**
* The absolute position of the data in the full stream. * The absolute position of the data in the full stream.
*/ */
public final long absoluteStreamPosition; public final long absoluteStreamPosition;
/** /**
* The position of the data when read from {@link #uri}. Always equal to * The position of the data when read from {@link #uri}.
* {@link #absoluteStreamPosition} if {@link #uriIsFullStream}. * <p>
* Always equal to {@link #absoluteStreamPosition} unless the {@link #uri} defines the location
* of a subset of the underyling data.
*/ */
public final long position; public final long position;
/** /**
...@@ -52,6 +62,10 @@ public final class DataSpec { ...@@ -52,6 +62,10 @@ public final class DataSpec {
* {@link DataSpec} is not intended to be used in conjunction with a cache. * {@link DataSpec} is not intended to be used in conjunction with a cache.
*/ */
public final String key; public final String key;
/**
* Request flags. Currently {@link #FLAG_ALLOW_GZIP} is the only supported flag.
*/
public final int flags;
/** /**
* Construct a {@link DataSpec} for the given uri and with {@link #key} set to null. * Construct a {@link DataSpec} for the given uri and with {@link #key} set to null.
...@@ -59,11 +73,21 @@ public final class DataSpec { ...@@ -59,11 +73,21 @@ public final class DataSpec {
* @param uri {@link #uri}. * @param uri {@link #uri}.
*/ */
public DataSpec(Uri uri) { public DataSpec(Uri uri) {
this(uri, 0, C.LENGTH_UNBOUNDED, null); this(uri, 0);
} }
/** /**
* Construct a {@link DataSpec} for which {@link #uriIsFullStream} is true. * Construct a {@link DataSpec} for the given uri and with {@link #key} set to null.
*
* @param uri {@link #uri}.
* @param flags {@link #flags}.
*/
public DataSpec(Uri uri, int flags) {
this(uri, 0, C.LENGTH_UNBOUNDED, null, flags);
}
/**
* Construct a {@link DataSpec} where {@link #position} equals {@link #absoluteStreamPosition}.
* *
* @param uri {@link #uri}. * @param uri {@link #uri}.
* @param absoluteStreamPosition {@link #absoluteStreamPosition}, equal to {@link #position}. * @param absoluteStreamPosition {@link #absoluteStreamPosition}, equal to {@link #position}.
...@@ -71,50 +95,50 @@ public final class DataSpec { ...@@ -71,50 +95,50 @@ public final class DataSpec {
* @param key {@link #key}. * @param key {@link #key}.
*/ */
public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key) { public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key) {
this(uri, absoluteStreamPosition, length, key, absoluteStreamPosition, true); this(uri, absoluteStreamPosition, absoluteStreamPosition, length, key, 0);
} }
/** /**
* Construct a {@link DataSpec} for which {@link #uriIsFullStream} is false. * Construct a {@link DataSpec} where {@link #position} equals {@link #absoluteStreamPosition}.
* *
* @param uri {@link #uri}. * @param uri {@link #uri}.
* @param absoluteStreamPosition {@link #absoluteStreamPosition}. * @param absoluteStreamPosition {@link #absoluteStreamPosition}, equal to {@link #position}.
* @param length {@link #length}. * @param length {@link #length}.
* @param key {@link #key}. * @param key {@link #key}.
* @param position {@link #position}. * @param flags {@link #flags}.
*/ */
public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, long position) { public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, int flags) {
this(uri, absoluteStreamPosition, length, key, position, false); this(uri, absoluteStreamPosition, absoluteStreamPosition, length, key, flags);
} }
/** /**
* Construct a {@link DataSpec}. * Construct a {@link DataSpec} where {@link #position} may differ from
* {@link #absoluteStreamPosition}.
* *
* @param uri {@link #uri}. * @param uri {@link #uri}.
* @param absoluteStreamPosition {@link #absoluteStreamPosition}. * @param absoluteStreamPosition {@link #absoluteStreamPosition}.
* @param position {@link #position}.
* @param length {@link #length}. * @param length {@link #length}.
* @param key {@link #key}. * @param key {@link #key}.
* @param position {@link #position}. * @param flags {@link #flags}.
* @param uriIsFullStream {@link #uriIsFullStream}.
*/ */
public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, long position, public DataSpec(Uri uri, long absoluteStreamPosition, long position, long length, String key,
boolean uriIsFullStream) { int flags) {
Assertions.checkArgument(absoluteStreamPosition >= 0); Assertions.checkArgument(absoluteStreamPosition >= 0);
Assertions.checkArgument(position >= 0); Assertions.checkArgument(position >= 0);
Assertions.checkArgument(length > 0 || length == C.LENGTH_UNBOUNDED); Assertions.checkArgument(length > 0 || length == C.LENGTH_UNBOUNDED);
Assertions.checkArgument(absoluteStreamPosition == position || !uriIsFullStream);
this.uri = uri; this.uri = uri;
this.uriIsFullStream = uriIsFullStream;
this.absoluteStreamPosition = absoluteStreamPosition; this.absoluteStreamPosition = absoluteStreamPosition;
this.position = position; this.position = position;
this.length = length; this.length = length;
this.key = key; this.key = key;
this.flags = flags;
} }
@Override @Override
public String toString() { public String toString() {
return "DataSpec[" + uri + ", " + uriIsFullStream + ", " + absoluteStreamPosition + ", " + return "DataSpec[" + uri + ", " + ", " + absoluteStreamPosition + ", " +
position + ", " + length + ", " + key + "]"; position + ", " + length + ", " + key + ", " + flags + "]";
} }
} }
...@@ -132,19 +132,6 @@ public class DefaultHttpDataSource implements HttpDataSource { ...@@ -132,19 +132,6 @@ public class DefaultHttpDataSource implements HttpDataSource {
} }
} }
/*
* TODO: If the server uses gzip compression when serving the response, this may end up returning
* the size of the compressed response, where-as it should be returning the decompressed size or
* -1. See: developer.android.com/reference/java/net/HttpURLConnection.html
*
* To fix this we should:
*
* 1. Explicitly require no compression for media requests (since media should be compressed
* already) by setting the Accept-Encoding header to "identity"
* 2. In other cases, for example when requesting manifests, we don't want to disable compression.
* For these cases we should ensure that we return -1 here (and avoid performing any sanity
* checks on the content length).
*/
@Override @Override
public long open(DataSpec dataSpec) throws HttpDataSourceException { public long open(DataSpec dataSpec) throws HttpDataSourceException {
this.dataSpec = dataSpec; this.dataSpec = dataSpec;
...@@ -177,9 +164,9 @@ public class DefaultHttpDataSource implements HttpDataSource { ...@@ -177,9 +164,9 @@ public class DefaultHttpDataSource implements HttpDataSource {
throw new InvalidContentTypeException(contentType, dataSpec); throw new InvalidContentTypeException(contentType, dataSpec);
} }
if ((dataSpec.flags & DataSpec.FLAG_ALLOW_GZIP) == 0) {
long contentLength = getContentLength(connection); long contentLength = getContentLength(connection);
dataLength = dataSpec.length == C.LENGTH_UNBOUNDED ? contentLength : dataSpec.length; dataLength = dataSpec.length == C.LENGTH_UNBOUNDED ? contentLength : dataSpec.length;
if (dataSpec.length != C.LENGTH_UNBOUNDED && contentLength != C.LENGTH_UNBOUNDED if (dataSpec.length != C.LENGTH_UNBOUNDED && contentLength != C.LENGTH_UNBOUNDED
&& contentLength != dataSpec.length) { && contentLength != dataSpec.length) {
// The DataSpec specified a length and we resolved a length from the response headers, but // The DataSpec specified a length and we resolved a length from the response headers, but
...@@ -188,6 +175,13 @@ public class DefaultHttpDataSource implements HttpDataSource { ...@@ -188,6 +175,13 @@ public class DefaultHttpDataSource implements HttpDataSource {
throw new HttpDataSourceException( throw new HttpDataSourceException(
new UnexpectedLengthException(dataSpec.length, contentLength), dataSpec); new UnexpectedLengthException(dataSpec.length, contentLength), dataSpec);
} }
} else {
// Gzip is enabled. If the server opts to use gzip then the content length in the response
// will be that of the compressed data, which isn't what we want. Furthermore, there isn't a
// reliable way to determine whether the gzip was used or not. Hence we always treat the
// length as unknown.
dataLength = C.LENGTH_UNBOUNDED;
}
try { try {
inputStream = connection.getInputStream(); inputStream = connection.getInputStream();
...@@ -301,6 +295,9 @@ public class DefaultHttpDataSource implements HttpDataSource { ...@@ -301,6 +295,9 @@ public class DefaultHttpDataSource implements HttpDataSource {
} }
setRangeHeader(connection, dataSpec); setRangeHeader(connection, dataSpec);
connection.setRequestProperty("User-Agent", userAgent); connection.setRequestProperty("User-Agent", userAgent);
if ((dataSpec.flags & DataSpec.FLAG_ALLOW_GZIP) == 0) {
connection.setRequestProperty("Accept-Encoding", "identity");
}
connection.connect(); connection.connect();
return connection; return connection;
} }
......
...@@ -63,7 +63,7 @@ public final class NetworkLoadable<T> implements Loadable { ...@@ -63,7 +63,7 @@ public final class NetworkLoadable<T> implements Loadable {
public NetworkLoadable(String url, HttpDataSource httpDataSource, Parser<T> parser) { public NetworkLoadable(String url, HttpDataSource httpDataSource, Parser<T> parser) {
this.httpDataSource = httpDataSource; this.httpDataSource = httpDataSource;
this.parser = parser; this.parser = parser;
dataSpec = new DataSpec(Uri.parse(url)); dataSpec = new DataSpec(Uri.parse(url), DataSpec.FLAG_ALLOW_GZIP);
} }
/** /**
......
...@@ -42,8 +42,8 @@ public final class TeeDataSource implements DataSource { ...@@ -42,8 +42,8 @@ public final class TeeDataSource implements DataSource {
long dataLength = upstream.open(dataSpec); long dataLength = upstream.open(dataSpec);
if (dataSpec.length == C.LENGTH_UNBOUNDED && dataLength != C.LENGTH_UNBOUNDED) { if (dataSpec.length == C.LENGTH_UNBOUNDED && dataLength != C.LENGTH_UNBOUNDED) {
// Reconstruct dataSpec in order to provide the resolved length to the sink. // Reconstruct dataSpec in order to provide the resolved length to the sink.
dataSpec = new DataSpec(dataSpec.uri, dataSpec.absoluteStreamPosition, dataLength, dataSpec = new DataSpec(dataSpec.uri, dataSpec.absoluteStreamPosition, dataSpec.position,
dataSpec.key, dataSpec.position, dataSpec.uriIsFullStream); dataLength, dataSpec.key, dataSpec.flags);
} }
dataSink.open(dataSpec); dataSink.open(dataSpec);
return dataLength; return dataLength;
......
...@@ -22,7 +22,6 @@ import com.google.android.exoplayer.upstream.DataSpec; ...@@ -22,7 +22,6 @@ import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.FileDataSource; import com.google.android.exoplayer.upstream.FileDataSource;
import com.google.android.exoplayer.upstream.TeeDataSource; import com.google.android.exoplayer.upstream.TeeDataSource;
import com.google.android.exoplayer.upstream.cache.CacheDataSink.CacheDataSinkException; import com.google.android.exoplayer.upstream.cache.CacheDataSink.CacheDataSinkException;
import com.google.android.exoplayer.util.Assertions;
import android.net.Uri; import android.net.Uri;
import android.util.Log; import android.util.Log;
...@@ -64,6 +63,7 @@ public final class CacheDataSource implements DataSource { ...@@ -64,6 +63,7 @@ public final class CacheDataSource implements DataSource {
private DataSource currentDataSource; private DataSource currentDataSource;
private Uri uri; private Uri uri;
private int flags;
private String key; private String key;
private long readPosition; private long readPosition;
private long bytesRemaining; private long bytesRemaining;
...@@ -125,9 +125,9 @@ public final class CacheDataSource implements DataSource { ...@@ -125,9 +125,9 @@ public final class CacheDataSource implements DataSource {
@Override @Override
public long open(DataSpec dataSpec) throws IOException { public long open(DataSpec dataSpec) throws IOException {
Assertions.checkState(dataSpec.uriIsFullStream);
try { try {
uri = dataSpec.uri; uri = dataSpec.uri;
flags = dataSpec.flags;
key = dataSpec.key; key = dataSpec.key;
readPosition = dataSpec.position; readPosition = dataSpec.position;
bytesRemaining = dataSpec.length; bytesRemaining = dataSpec.length;
...@@ -201,19 +201,19 @@ public final class CacheDataSource implements DataSource { ...@@ -201,19 +201,19 @@ public final class CacheDataSource implements DataSource {
// The data is locked in the cache, or we're ignoring the cache. Bypass the cache and read // The data is locked in the cache, or we're ignoring the cache. Bypass the cache and read
// from upstream. // from upstream.
currentDataSource = upstreamDataSource; currentDataSource = upstreamDataSource;
dataSpec = new DataSpec(uri, readPosition, bytesRemaining, key); dataSpec = new DataSpec(uri, readPosition, bytesRemaining, key, flags);
} else if (span.isCached) { } else if (span.isCached) {
// Data is cached, read from cache. // Data is cached, read from cache.
Uri fileUri = Uri.fromFile(span.file); Uri fileUri = Uri.fromFile(span.file);
long filePosition = readPosition - span.position; long filePosition = readPosition - span.position;
long length = Math.min(span.length - filePosition, bytesRemaining); long length = Math.min(span.length - filePosition, bytesRemaining);
dataSpec = new DataSpec(fileUri, readPosition, length, key, filePosition); dataSpec = new DataSpec(fileUri, readPosition, filePosition, length, key, flags);
currentDataSource = cacheReadDataSource; currentDataSource = cacheReadDataSource;
} else { } else {
// Data is not cached, and data is not locked, read from upstream with cache backing. // Data is not cached, and data is not locked, read from upstream with cache backing.
lockedSpan = span; lockedSpan = span;
long length = span.isOpenEnded() ? bytesRemaining : Math.min(span.length, bytesRemaining); long length = span.isOpenEnded() ? bytesRemaining : Math.min(span.length, bytesRemaining);
dataSpec = new DataSpec(uri, readPosition, length, key); dataSpec = new DataSpec(uri, readPosition, length, key, flags);
currentDataSource = cacheWriteDataSource != null ? cacheWriteDataSource currentDataSource = cacheWriteDataSource != null ? cacheWriteDataSource
: upstreamDataSource; : upstreamDataSource;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment