Commit 434a2733 by Oliver Woodman

OkHttp extension modifications.

1. Change package name (rm "datasource")
2. Require injection of OkHttpClient through all constructors, and
   remove allowCrossProtocolRedirect/connectTimeout/readTimeout
   constructor arguments. The client should be configured with these
   prior to injection.
3. Fix code style to be consistent with the project.
4. Simplify call to get contentLength. I'm pretty sure okhttp returns
   the correct value when gzip is enabled, so there's no need to check
   this in the data source.
5. Misc cleanups.
parent 2112bc24
...@@ -13,456 +13,366 @@ ...@@ -13,456 +13,366 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer.ext.datasource.okhttp; package com.google.android.exoplayer.ext.okhttp;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer.upstream.HttpDataSource; import com.google.android.exoplayer.upstream.HttpDataSource;
import com.google.android.exoplayer.upstream.TransferListener; import com.google.android.exoplayer.upstream.TransferListener;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Predicate; import com.google.android.exoplayer.util.Predicate;
import com.squareup.okhttp.CacheControl; import com.squareup.okhttp.CacheControl;
import com.squareup.okhttp.HttpUrl; import com.squareup.okhttp.HttpUrl;
import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request; import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response; import com.squareup.okhttp.Response;
import com.squareup.okhttp.internal.Util;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.net.ProtocolException;
import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import static com.squareup.okhttp.internal.Util.closeQuietly;
/** /**
* A {@link HttpDataSource} that uses Square's {@link OkHttpClient}. * An {@link HttpDataSource} that delegates to Square's {@link OkHttpClient}.
* <p/>
* By default this implementation will follow cross-protocol redirects (i.e. redirects from
* HTTP to HTTPS or vice versa). Cross-protocol redirects can be disabled by using the
* {@link #OkHttpDataSource(String, Predicate, TransferListener, int, int, boolean, OkHttpClient, CacheControl)}
* constructor and passing {@code false} as the sixth argument.
*/ */
public class OkHttpDataSource implements HttpDataSource { public class OkHttpDataSource implements HttpDataSource {
/** private static final AtomicReference<byte[]> skipBufferReference = new AtomicReference<>();
* The default connection timeout, in milliseconds.
*/ private final OkHttpClient okHttpClient;
private static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS; private final String userAgent;
/** private final Predicate<String> contentTypePredicate;
* The default read timeout, in milliseconds. private final TransferListener listener;
*/ private final CacheControl cacheControl;
private static final int DEFAULT_READ_TIMEOUT_MILLIS = DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS; private final HashMap<String, String> requestProperties;
private static final String TAG = "OkHttpDataSource"; private DataSpec dataSpec;
private static final AtomicReference<byte[]> skipBufferReference = new AtomicReference<>(); private Response response;
private final String userAgent; private InputStream responseByteStream;
private boolean opened;
private final Predicate<String> contentTypePredicate;
private final HashMap<String, String> requestProperties; private long bytesToSkip;
private final CacheControl cacheControl; private long bytesToRead;
private final TransferListener listener;
private long bytesSkipped;
private DataSpec dataSpec; private long bytesRead;
private static OkHttpClient okHttpClient;
private Response response; /**
private boolean opened; * @param client An {@link OkHttpClient} for use by the source.
* @param userAgent The User-Agent string that should be used.
private long bytesToSkip; * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
private long bytesToRead; * predicate then a
* {@link com.google.android.exoplayer.upstream.HttpDataSource.InvalidContentTypeException} is
private long bytesSkipped; * thrown from {@link #open(DataSpec)}.
private long bytesRead; */
public OkHttpDataSource(OkHttpClient client, String userAgent,
/** Predicate<String> contentTypePredicate) {
* @param userAgent The User-Agent string that should be used. this(client, userAgent, contentTypePredicate, null);
* @param contentTypePredicate An optional {@link Predicate}. If a content type is }
* rejected by the predicate then a {@link InvalidContentTypeException} is
* thrown from {@link #open(DataSpec)}. /**
*/ * @param client An {@link OkHttpClient} for use by the source.
public OkHttpDataSource(String userAgent, Predicate<String> contentTypePredicate) { * @param userAgent The User-Agent string that should be used.
this(userAgent, contentTypePredicate, null); * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a
* {@link com.google.android.exoplayer.upstream.HttpDataSource.InvalidContentTypeException} is
* thrown from {@link #open(DataSpec)}.
* @param listener An optional listener.
*/
public OkHttpDataSource(OkHttpClient client, String userAgent,
Predicate<String> contentTypePredicate, TransferListener listener) {
this(client, userAgent, contentTypePredicate, listener, null);
}
/**
* @param client An {@link OkHttpClient} for use by the source.
* @param userAgent The User-Agent string that should be used.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a
* {@link com.google.android.exoplayer.upstream.HttpDataSource.InvalidContentTypeException} is
* thrown from {@link #open(DataSpec)}.
* @param listener An optional listener.
* @param cacheControl An optional {@link CacheControl} which sets all requests' Cache-Control
* header. For example, you could force the network response for all requests.
*
*/
public OkHttpDataSource(OkHttpClient client, String userAgent,
Predicate<String> contentTypePredicate, TransferListener listener,
CacheControl cacheControl) {
this.okHttpClient = Assertions.checkNotNull(client);
this.userAgent = Assertions.checkNotEmpty(userAgent);
this.contentTypePredicate = contentTypePredicate;
this.listener = listener;
this.cacheControl = cacheControl;
this.requestProperties = new HashMap<>();
}
@Override
public String getUri() {
return response == null ? null : response.request().urlString();
}
@Override
public Map<String, List<String>> getResponseHeaders() {
return response == null ? null : response.headers().toMultimap();
}
@Override
public void setRequestProperty(String name, String value) {
Assertions.checkNotNull(name);
Assertions.checkNotNull(value);
synchronized (requestProperties) {
requestProperties.put(name, value);
} }
}
/** @Override
* @param userAgent The User-Agent string that should be used. public void clearRequestProperty(String name) {
* @param contentTypePredicate An optional {@link Predicate}. If a content type is Assertions.checkNotNull(name);
* rejected by the predicate then a {@link InvalidContentTypeException} is synchronized (requestProperties) {
* thrown from {@link #open(DataSpec)}. requestProperties.remove(name);
* @param listener An optional listener.
*/
public OkHttpDataSource(String userAgent, Predicate<String> contentTypePredicate,
TransferListener listener) {
this(userAgent, contentTypePredicate, listener, DEFAULT_CONNECT_TIMEOUT_MILLIS,
DEFAULT_READ_TIMEOUT_MILLIS);
} }
}
/** @Override
* @param userAgent The User-Agent string that should be used. public void clearAllRequestProperties() {
* @param contentTypePredicate An optional {@link Predicate}. If a content type is synchronized (requestProperties) {
* rejected by the predicate then a {@link InvalidContentTypeException} is requestProperties.clear();
* thrown from {@link #open(DataSpec)}.
* @param listener An optional listener.
* @param connectTimeoutMillis The connection timeout, in milliseconds. A timeout of zero is
* interpreted as an infinite timeout.
* @param readTimeoutMillis The read timeout, in milliseconds. A timeout of zero is interpreted
* as an infinite timeout.
*/
public OkHttpDataSource(String userAgent, Predicate<String> contentTypePredicate,
TransferListener listener, int connectTimeoutMillis, int readTimeoutMillis) {
this(userAgent, contentTypePredicate, listener, connectTimeoutMillis, readTimeoutMillis, true, null, null);
} }
}
/**
* @param userAgent The User-Agent string that should be used. @Override
* @param contentTypePredicate An optional {@link Predicate}. If a content type is public long open(DataSpec dataSpec) throws HttpDataSourceException {
* rejected by the predicate then a {@link InvalidContentTypeException} is this.dataSpec = dataSpec;
* thrown from {@link #open(DataSpec)}. this.bytesRead = 0;
* @param listener An optional listener. this.bytesSkipped = 0;
* @param connectTimeoutMillis The connection timeout, in milliseconds. A timeout of zero is Request request = makeRequest(dataSpec);
* interpreted as an infinite timeout. Pass {@link #DEFAULT_CONNECT_TIMEOUT_MILLIS} to use try {
* the default value. response = okHttpClient.newCall(request).execute();
* @param readTimeoutMillis The read timeout, in milliseconds. A timeout of zero is interpreted responseByteStream = response.body().byteStream();
* as an infinite timeout. Pass {@link #DEFAULT_READ_TIMEOUT_MILLIS} to use the default value. } catch (IOException e) {
* @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
* to HTTPS and vice versa) are enabled. dataSpec);
* @param httpClient An optional {@link OkHttpClient}. Most applications can use a single OkHttpClient for all of
* their HTTP requests. Pass an {@link OkHttpClient} if you already have an
* {@link OkHttpClient} in your application, or you want some customized feature, such as
* monitor calls using {@link Interceptor}.
* @param cacheControl An optional {@link CacheControl} which sets all requests' Cache-Control header. For example,
* you could force the network response for all requests.
*/
public OkHttpDataSource(String userAgent, Predicate<String> contentTypePredicate,
TransferListener listener, int connectTimeoutMillis, int readTimeoutMillis,
boolean allowCrossProtocolRedirects, OkHttpClient httpClient, CacheControl cacheControl) {
this.userAgent = Assertions.checkNotEmpty(userAgent);
this.contentTypePredicate = contentTypePredicate;
this.listener = listener;
this.requestProperties = new HashMap<>();
if (httpClient != null) {
okHttpClient = httpClient;
} else if (okHttpClient == null) {
okHttpClient = new OkHttpClient();
}
okHttpClient.setConnectTimeout(connectTimeoutMillis, TimeUnit.MILLISECONDS);
okHttpClient.setReadTimeout(readTimeoutMillis, TimeUnit.MILLISECONDS);
if (!allowCrossProtocolRedirects) {
okHttpClient.setFollowSslRedirects(allowCrossProtocolRedirects);
}
this.cacheControl = cacheControl;
} }
@Override int responseCode = response.code();
public String getUri() {
return response == null ? null : response.request().urlString();
}
@Override // Check for a valid response code.
public Map<String, List<String>> getResponseHeaders() { if (!response.isSuccessful()) {
return response == null ? null : response.headers().toMultimap(); Map<String, List<String>> headers = request.headers().toMultimap();
closeConnectionQuietly();
throw new InvalidResponseCodeException(responseCode, headers, dataSpec);
} }
@Override // Check for a valid content type.
public void setRequestProperty(String name, String value) { String contentType = response.body().contentType().toString();
Assertions.checkNotNull(name); if (contentTypePredicate != null && !contentTypePredicate.evaluate(contentType)) {
Assertions.checkNotNull(value); closeConnectionQuietly();
synchronized (requestProperties) { throw new InvalidContentTypeException(contentType, dataSpec);
requestProperties.put(name, value);
}
} }
@Override // If we requested a range starting from a non-zero position and received a 200 rather than a
public void clearRequestProperty(String name) { // 206, then the server does not support partial requests. We'll need to manually skip to the
Assertions.checkNotNull(name); // requested position.
synchronized (requestProperties) { bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0;
requestProperties.remove(name);
} // Determine the length of the data to be read, after skipping.
try {
long contentLength = response.body().contentLength();
bytesToRead = dataSpec.length != C.LENGTH_UNBOUNDED ? dataSpec.length
: contentLength != -1 ? contentLength - bytesToSkip
: C.LENGTH_UNBOUNDED;
} catch (IOException e) {
closeConnectionQuietly();
throw new HttpDataSourceException(e, dataSpec);
} }
@Override opened = true;
public void clearAllRequestProperties() { if (listener != null) {
synchronized (requestProperties) { listener.onTransferStart();
requestProperties.clear();
}
} }
@Override return bytesToRead;
public long open(DataSpec dataSpec) throws HttpDataSourceException { }
this.dataSpec = dataSpec;
this.bytesRead = 0;
this.bytesSkipped = 0;
Request request = makeRequest(dataSpec);
try {
response = okHttpClient.newCall(request).execute();
} catch (IOException e) {
throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
dataSpec);
}
int responseCode = response.code();
// Check for a valid response code.
if (!response.isSuccessful()) {
Map<String, List<String>> headers = request.headers().toMultimap();
closeConnection();
throw new InvalidResponseCodeException(responseCode, headers, dataSpec);
}
// Check for a valid content type.
String contentType = response.body().contentType().toString();
if (contentTypePredicate != null && !contentTypePredicate.evaluate(contentType)) {
closeConnection();
throw new InvalidContentTypeException(contentType, dataSpec);
}
// If we requested a range starting from a non-zero position and received a 200 rather than a
// 206, then the server does not support partial requests. We'll need to manually skip to the
// requested position.
bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0;
// Determine the length of the data to be read, after skipping.
if ((dataSpec.flags & DataSpec.FLAG_ALLOW_GZIP) == 0) {
long contentLength = 0;
try {
contentLength = response.body().contentLength();
} catch (IOException e) {
closeConnection();
throw new HttpDataSourceException(e, dataSpec);
}
bytesToRead = dataSpec.length != C.LENGTH_UNBOUNDED ? dataSpec.length
: contentLength != C.LENGTH_UNBOUNDED ? contentLength - bytesToSkip
: C.LENGTH_UNBOUNDED;
} 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. Always use the dataSpec length
// in this case.
bytesToRead = dataSpec.length;
}
opened = true;
if (listener != null) {
listener.onTransferStart();
}
return bytesToRead;
}
@Override @Override
public int read(byte[] buffer, int offset, int readLength) throws HttpDataSourceException { public int read(byte[] buffer, int offset, int readLength) throws HttpDataSourceException {
try { try {
skipInternal(); skipInternal();
return readInternal(buffer, offset, readLength); return readInternal(buffer, offset, readLength);
} catch (IOException e) { } catch (IOException e) {
throw new HttpDataSourceException(e, dataSpec); throw new HttpDataSourceException(e, dataSpec);
}
} }
}
@Override
public void close() throws HttpDataSourceException { @Override
if (opened) { public void close() throws HttpDataSourceException {
opened = false; if (opened) {
if (listener != null) { opened = false;
listener.onTransferEnd(); if (listener != null) {
} listener.onTransferEnd();
closeConnection(); }
} closeConnectionQuietly();
} }
}
/**
* Returns the current connection, or null if the source is not currently opened. /**
* * Returns the number of bytes that have been skipped since the most recent call to
* @return The current open connection, or null. * {@link #open(DataSpec)}.
*/ *
protected final OkHttpClient getOkHttpClient() { * @return The number of bytes skipped.
return okHttpClient; */
protected final long bytesSkipped() {
return bytesSkipped;
}
/**
* Returns the number of bytes that have been read since the most recent call to
* {@link #open(DataSpec)}.
*
* @return The number of bytes read.
*/
protected final long bytesRead() {
return bytesRead;
}
/**
* Returns the number of bytes that are still to be read for the current {@link DataSpec}.
* <p>
* If the total length of the data being read is known, then this length minus {@code bytesRead()}
* is returned. If the total length is unknown, {@link C#LENGTH_UNBOUNDED} is returned.
*
* @return The remaining length, or {@link C#LENGTH_UNBOUNDED}.
*/
protected final long bytesRemaining() {
return bytesToRead == C.LENGTH_UNBOUNDED ? bytesToRead : bytesToRead - bytesRead;
}
/**
* Establishes a connection.
*/
private Request makeRequest(DataSpec dataSpec) {
long position = dataSpec.position;
long length = dataSpec.length;
boolean allowGzip = (dataSpec.flags & DataSpec.FLAG_ALLOW_GZIP) != 0;
HttpUrl url = HttpUrl.parse(dataSpec.uri.toString());
Request.Builder builder = new Request.Builder().url(url);
if (cacheControl != null) {
builder.cacheControl(cacheControl);
} }
synchronized (requestProperties) {
/** for (Map.Entry<String, String> property : requestProperties.entrySet()) {
* Returns the number of bytes that have been skipped since the most recent call to builder.addHeader(property.getKey(), property.getValue());
* {@link #open(DataSpec)}. }
*
* @return The number of bytes skipped.
*/
protected final long bytesSkipped() {
return bytesSkipped;
} }
if (!(position == 0 && length == C.LENGTH_UNBOUNDED)) {
/** String rangeRequest = "bytes=" + position + "-";
* Returns the number of bytes that have been read since the most recent call to if (length != C.LENGTH_UNBOUNDED) {
* {@link #open(DataSpec)}. rangeRequest += (position + length - 1);
* }
* @return The number of bytes read. builder.addHeader("Range", rangeRequest);
*/
protected final long bytesRead() {
return bytesRead;
} }
builder.addHeader("User-Agent", userAgent);
/** if (!allowGzip) {
* Returns the number of bytes that are still to be read for the current {@link DataSpec}. builder.addHeader("Accept-Encoding", "identity");
* <p/> }
* If the total length of the data being read is known, then this length minus {@code bytesRead()} if (dataSpec.postBody != null) {
* is returned. If the total length is unknown, {@link C#LENGTH_UNBOUNDED} is returned. builder.post(RequestBody.create(null, dataSpec.postBody));
* }
* @return The remaining length, or {@link C#LENGTH_UNBOUNDED}. return builder.build();
*/ }
protected final long bytesRemaining() {
return bytesToRead == C.LENGTH_UNBOUNDED ? bytesToRead : bytesToRead - bytesRead; /**
* Skips any bytes that need skipping. Else does nothing.
* <p>
* This implementation is based roughly on {@code libcore.io.Streams.skipByReading()}.
*
* @throws InterruptedIOException If the thread is interrupted during the operation.
* @throws EOFException If the end of the input stream is reached before the bytes are skipped.
*/
private void skipInternal() throws IOException {
if (bytesSkipped == bytesToSkip) {
return;
} }
private Request makeRequest(DataSpec dataSpec) { // Acquire the shared skip buffer.
long position = dataSpec.position; byte[] skipBuffer = skipBufferReference.getAndSet(null);
long length = dataSpec.length; if (skipBuffer == null) {
boolean allowGzip = (dataSpec.flags & DataSpec.FLAG_ALLOW_GZIP) != 0; skipBuffer = new byte[4096];
HttpUrl url = HttpUrl.parse(dataSpec.uri.toString());
Request.Builder builder = new Request.Builder()
.url(url);
if (cacheControl != null) {
builder.cacheControl(cacheControl);
}
synchronized (requestProperties) {
for (Map.Entry<String, String> property : requestProperties.entrySet()) {
builder.addHeader(property.getKey(), property.getValue());
}
}
if (!(position == 0 && length == C.LENGTH_UNBOUNDED)) {
String rangeRequest = "bytes=" + position + "-";
if (length != C.LENGTH_UNBOUNDED) {
rangeRequest += (position + length - 1);
}
builder.addHeader("Range", rangeRequest);
}
builder.addHeader("User-Agent", userAgent);
if (!allowGzip) {
builder.addHeader("Accept-Encoding", "identity");
}
if (dataSpec.postBody != null) {
builder.post(RequestBody.create(null, dataSpec.postBody));
}
return builder.build();
} }
/** while (bytesSkipped != bytesToSkip) {
* Handles a redirect. int readLength = (int) Math.min(bytesToSkip - bytesSkipped, skipBuffer.length);
* int read = responseByteStream.read(skipBuffer, 0, readLength);
* @param originalUrl The original URL. if (Thread.interrupted()) {
* @param location The Location header in the response. throw new InterruptedIOException();
* @return The next URL. }
* @throws IOException If redirection isn't possible. if (read == -1) {
*/ throw new EOFException();
private static URL handleRedirect(URL originalUrl, String location) throws IOException { }
if (location == null) { bytesSkipped += read;
throw new ProtocolException("Null location redirect"); if (listener != null) {
} listener.onBytesTransferred(read);
// Form the new url. }
URL url = new URL(originalUrl, location);
// Check that the protocol of the new url is supported.
String protocol = url.getProtocol();
if (!"https".equals(protocol) && !"http".equals(protocol)) {
throw new ProtocolException("Unsupported protocol redirect: " + protocol);
}
// Currently this method is only called if allowCrossProtocolRedirects is true, and so the code
// below isn't required. If we ever decide to handle redirects ourselves when cross-protocol
// redirects are disabled, we'll need to uncomment this block of code.
// if (!allowCrossProtocolRedirects && !protocol.equals(originalUrl.getProtocol())) {
// throw new ProtocolException("Disallowed cross-protocol redirect ("
// + originalUrl.getProtocol() + " to " + protocol + ")");
// }
return url;
} }
/** // Release the shared skip buffer.
* Skips any bytes that need skipping. Else does nothing. skipBufferReference.set(skipBuffer);
* <p/> }
* This implementation is based roughly on {@code libcore.io.Streams.skipByReading()}.
* /**
* @throws InterruptedIOException If the thread is interrupted during the operation. * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at
* @throws EOFException If the end of the input stream is reached before the bytes are skipped. * index {@code offset}.
*/ * <p>
private void skipInternal() throws IOException { * This method blocks until at least one byte of data can be read, the end of the opened range is
if (bytesSkipped == bytesToSkip) { * detected, or an exception is thrown.
return; *
} * @param buffer The buffer into which the read data should be stored.
* @param offset The start offset into {@code buffer} at which data should be written.
// Acquire the shared skip buffer. * @param readLength The maximum number of bytes to read.
byte[] skipBuffer = skipBufferReference.getAndSet(null); * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the end of the opened
if (skipBuffer == null) { * range is reached.
skipBuffer = new byte[4096]; * @throws IOException If an error occurs reading from the source.
} */
private int readInternal(byte[] buffer, int offset, int readLength) throws IOException {
while (bytesSkipped != bytesToSkip) { readLength = bytesToRead == C.LENGTH_UNBOUNDED ? readLength
int readLength = (int) Math.min(bytesToSkip - bytesSkipped, skipBuffer.length); : (int) Math.min(readLength, bytesToRead - bytesRead);
int read = response.body().byteStream().read(skipBuffer, 0, readLength); if (readLength == 0) {
if (Thread.interrupted()) { // We've read all of the requested data.
throw new InterruptedIOException(); return C.RESULT_END_OF_INPUT;
}
if (read == -1) {
throw new EOFException();
}
bytesSkipped += read;
if (listener != null) {
listener.onBytesTransferred(read);
}
}
// Release the shared skip buffer.
skipBufferReference.set(skipBuffer);
} }
/** int read = responseByteStream.read(buffer, offset, readLength);
* Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at if (read == -1) {
* index {@code offset}. if (bytesToRead != C.LENGTH_UNBOUNDED && bytesToRead != bytesRead) {
* <p/> // The server closed the connection having not sent sufficient data.
* This method blocks until at least one byte of data can be read, the end of the opened range is throw new EOFException();
* detected, or an exception is thrown. }
* return C.RESULT_END_OF_INPUT;
* @param buffer The buffer into which the read data should be stored.
* @param offset The start offset into {@code buffer} at which data should be written.
* @param readLength The maximum number of bytes to read.
* @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the end of the opened
* range is reached.
* @throws IOException If an error occurs reading from the source.
*/
private int readInternal(byte[] buffer, int offset, int readLength) throws IOException {
readLength = bytesToRead == C.LENGTH_UNBOUNDED ? readLength
: (int) Math.min(readLength, bytesToRead - bytesRead);
if (readLength == 0) {
// We've read all of the requested data.
return C.RESULT_END_OF_INPUT;
}
int read = response.body().byteStream().read(buffer, offset, readLength);
if (read == -1) {
if (bytesToRead != C.LENGTH_UNBOUNDED && bytesToRead != bytesRead) {
// The server closed the connection having not sent sufficient data.
throw new EOFException();
}
return C.RESULT_END_OF_INPUT;
}
bytesRead += read;
if (listener != null) {
listener.onBytesTransferred(read);
}
return read;
} }
/** bytesRead += read;
* Closes the current connection, if there is one. if (listener != null) {
*/ listener.onBytesTransferred(read);
private void closeConnection() {
closeQuietly(response.body());
response = null;
} }
return read;
}
/**
* Closes the current connection quietly, if there is one.
*/
private void closeConnectionQuietly() {
Util.closeQuietly(response.body());
response = null;
responseByteStream = null;
}
} }
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