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
This commit is contained in:
Oliver Woodman 2015-03-03 17:14:48 +00:00
parent 3868b1d4cb
commit 0c6566bce7
7 changed files with 80 additions and 58 deletions

View file

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

View file

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

View file

@ -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.
* <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 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}.
* <p>
* 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 + "]";
}
}

View file

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

View file

@ -63,7 +63,7 @@ public final class NetworkLoadable<T> implements Loadable {
public NetworkLoadable(String url, HttpDataSource httpDataSource, Parser<T> parser) {
this.httpDataSource = httpDataSource;
this.parser = parser;
dataSpec = new DataSpec(Uri.parse(url));
dataSpec = new DataSpec(Uri.parse(url), DataSpec.FLAG_ALLOW_GZIP);
}
/**

View file

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

View file

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