diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 5aa0efe5e2..82a9f98c51 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -438,13 +438,14 @@ public class HlsChunkSource { private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex) { 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, mediaPlaylistUri.toString()); } 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); } diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/DataSourceStream.java b/library/src/main/java/com/google/android/exoplayer/upstream/DataSourceStream.java index 5c4dcd65b2..33519d4e6c 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/DataSourceStream.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/DataSourceStream.java @@ -230,7 +230,7 @@ public final class DataSourceStream implements Loadable, NonBlockingInputStream long remainingLength = resolvedLength != C.LENGTH_UNBOUNDED ? resolvedLength - loadPosition : C.LENGTH_UNBOUNDED; loadDataSpec = new DataSpec(dataSpec.uri, dataSpec.position + loadPosition, - remainingLength, dataSpec.key); + remainingLength, dataSpec.key, dataSpec.flags); dataSource.open(loadDataSpec); } diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/DataSpec.java b/library/src/main/java/com/google/android/exoplayer/upstream/DataSpec.java index ff3b7dda0d..a153d955cb 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/DataSpec.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/DataSpec.java @@ -25,22 +25,32 @@ import android.net.Uri; */ public final class DataSpec { + /** + * Permits an underlying network stack to request that the server use gzip compression. + *

+ * 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. + *

+ * 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 static final int FLAG_ALLOW_GZIP = 1; + /** * Identifies the source from which data should be read. */ public final Uri uri; - /** - * True if the data at {@link #uri} is the full stream. False otherwise. An example where this - * may be false is if {@link #uri} defines the location of a cached part of the stream. - */ - public final boolean uriIsFullStream; /** * The absolute position of the data in the full stream. */ public final long absoluteStreamPosition; /** - * The position of the data when read from {@link #uri}. Always equal to - * {@link #absoluteStreamPosition} if {@link #uriIsFullStream}. + * The position of the data when read from {@link #uri}. + *

+ * Always equal to {@link #absoluteStreamPosition} unless the {@link #uri} defines the location + * of a subset of the underyling data. */ public final long position; /** @@ -52,6 +62,10 @@ public final class DataSpec { * {@link DataSpec} is not intended to be used in conjunction with a cache. */ 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. @@ -59,11 +73,21 @@ public final class DataSpec { * @param uri {@link #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 absoluteStreamPosition {@link #absoluteStreamPosition}, equal to {@link #position}. @@ -71,50 +95,50 @@ public final class DataSpec { * @param key {@link #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 absoluteStreamPosition {@link #absoluteStreamPosition}. + * @param absoluteStreamPosition {@link #absoluteStreamPosition}, equal to {@link #position}. * @param length {@link #length}. * @param key {@link #key}. - * @param position {@link #position}. + * @param flags {@link #flags}. */ - public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, long position) { - this(uri, absoluteStreamPosition, length, key, position, false); + public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, int flags) { + 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 absoluteStreamPosition {@link #absoluteStreamPosition}. + * @param position {@link #position}. * @param length {@link #length}. * @param key {@link #key}. - * @param position {@link #position}. - * @param uriIsFullStream {@link #uriIsFullStream}. + * @param flags {@link #flags}. */ - public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, long position, - boolean uriIsFullStream) { + public DataSpec(Uri uri, long absoluteStreamPosition, long position, long length, String key, + int flags) { Assertions.checkArgument(absoluteStreamPosition >= 0); Assertions.checkArgument(position >= 0); Assertions.checkArgument(length > 0 || length == C.LENGTH_UNBOUNDED); - Assertions.checkArgument(absoluteStreamPosition == position || !uriIsFullStream); this.uri = uri; - this.uriIsFullStream = uriIsFullStream; this.absoluteStreamPosition = absoluteStreamPosition; this.position = position; this.length = length; this.key = key; + this.flags = flags; } @Override public String toString() { - return "DataSpec[" + uri + ", " + uriIsFullStream + ", " + absoluteStreamPosition + ", " + - position + ", " + length + ", " + key + "]"; + return "DataSpec[" + uri + ", " + ", " + absoluteStreamPosition + ", " + + position + ", " + length + ", " + key + ", " + flags + "]"; } } diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultHttpDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultHttpDataSource.java index 6fbab8982e..8e0a4b75ec 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultHttpDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultHttpDataSource.java @@ -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 public long open(DataSpec dataSpec) throws HttpDataSourceException { this.dataSpec = dataSpec; @@ -177,16 +164,23 @@ public class DefaultHttpDataSource implements HttpDataSource { throw new InvalidContentTypeException(contentType, dataSpec); } - long contentLength = getContentLength(connection); - dataLength = dataSpec.length == C.LENGTH_UNBOUNDED ? contentLength : dataSpec.length; - - if (dataSpec.length != C.LENGTH_UNBOUNDED && contentLength != C.LENGTH_UNBOUNDED - && contentLength != dataSpec.length) { - // The DataSpec specified a length and we resolved a length from the response headers, but - // the two lengths do not match. - closeConnection(); - throw new HttpDataSourceException( - new UnexpectedLengthException(dataSpec.length, contentLength), dataSpec); + if ((dataSpec.flags & DataSpec.FLAG_ALLOW_GZIP) == 0) { + long contentLength = getContentLength(connection); + dataLength = dataSpec.length == C.LENGTH_UNBOUNDED ? contentLength : dataSpec.length; + if (dataSpec.length != C.LENGTH_UNBOUNDED && contentLength != C.LENGTH_UNBOUNDED + && contentLength != dataSpec.length) { + // The DataSpec specified a length and we resolved a length from the response headers, but + // the two lengths do not match. + closeConnection(); + throw new HttpDataSourceException( + 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 { @@ -301,6 +295,9 @@ public class DefaultHttpDataSource implements HttpDataSource { } setRangeHeader(connection, dataSpec); connection.setRequestProperty("User-Agent", userAgent); + if ((dataSpec.flags & DataSpec.FLAG_ALLOW_GZIP) == 0) { + connection.setRequestProperty("Accept-Encoding", "identity"); + } connection.connect(); return connection; } diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/NetworkLoadable.java b/library/src/main/java/com/google/android/exoplayer/upstream/NetworkLoadable.java index 5e8058f6dd..d4f4e6abf7 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/NetworkLoadable.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/NetworkLoadable.java @@ -63,7 +63,7 @@ public final class NetworkLoadable implements Loadable { public NetworkLoadable(String url, HttpDataSource httpDataSource, Parser parser) { this.httpDataSource = httpDataSource; this.parser = parser; - dataSpec = new DataSpec(Uri.parse(url)); + dataSpec = new DataSpec(Uri.parse(url), DataSpec.FLAG_ALLOW_GZIP); } /** diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/TeeDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/TeeDataSource.java index cbb571f308..2e140db6c8 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/TeeDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/TeeDataSource.java @@ -42,8 +42,8 @@ public final class TeeDataSource implements DataSource { long dataLength = upstream.open(dataSpec); if (dataSpec.length == C.LENGTH_UNBOUNDED && dataLength != C.LENGTH_UNBOUNDED) { // Reconstruct dataSpec in order to provide the resolved length to the sink. - dataSpec = new DataSpec(dataSpec.uri, dataSpec.absoluteStreamPosition, dataLength, - dataSpec.key, dataSpec.position, dataSpec.uriIsFullStream); + dataSpec = new DataSpec(dataSpec.uri, dataSpec.absoluteStreamPosition, dataSpec.position, + dataLength, dataSpec.key, dataSpec.flags); } dataSink.open(dataSpec); return dataLength; diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/cache/CacheDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/cache/CacheDataSource.java index 5842d742ab..63a3763133 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/cache/CacheDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/cache/CacheDataSource.java @@ -22,7 +22,6 @@ import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.FileDataSource; import com.google.android.exoplayer.upstream.TeeDataSource; import com.google.android.exoplayer.upstream.cache.CacheDataSink.CacheDataSinkException; -import com.google.android.exoplayer.util.Assertions; import android.net.Uri; import android.util.Log; @@ -64,6 +63,7 @@ public final class CacheDataSource implements DataSource { private DataSource currentDataSource; private Uri uri; + private int flags; private String key; private long readPosition; private long bytesRemaining; @@ -125,9 +125,9 @@ public final class CacheDataSource implements DataSource { @Override public long open(DataSpec dataSpec) throws IOException { - Assertions.checkState(dataSpec.uriIsFullStream); try { uri = dataSpec.uri; + flags = dataSpec.flags; key = dataSpec.key; readPosition = dataSpec.position; bytesRemaining = dataSpec.length; @@ -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 // from upstream. currentDataSource = upstreamDataSource; - dataSpec = new DataSpec(uri, readPosition, bytesRemaining, key); + dataSpec = new DataSpec(uri, readPosition, bytesRemaining, key, flags); } else if (span.isCached) { // Data is cached, read from cache. Uri fileUri = Uri.fromFile(span.file); long filePosition = readPosition - span.position; 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; } else { // Data is not cached, and data is not locked, read from upstream with cache backing. lockedSpan = span; 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 : upstreamDataSource; }