diff --git a/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java b/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java index ccd4dec191..dcc5bc9b97 100644 --- a/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java +++ b/extensions/cronet/src/androidTest/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceTest.java @@ -174,10 +174,7 @@ public final class CronetDataSourceTest { @Test(expected = IllegalStateException.class) public void testOpeningTwiceThrows() throws HttpDataSourceException { mockResponseStartSuccess(); - - assertConnectionState(CronetDataSource.IDLE_CONNECTION); dataSourceUnderTest.open(testDataSpec); - assertConnectionState(CronetDataSource.OPEN_CONNECTION); dataSourceUnderTest.open(testDataSpec); } @@ -205,7 +202,7 @@ public final class CronetDataSourceTest { dataSourceUnderTest.onFailed( mockUrlRequest, testUrlResponseInfo, - null); + mockUrlRequestException); dataSourceUnderTest.onResponseStarted( mockUrlRequest2, testUrlResponseInfo); @@ -253,13 +250,10 @@ public final class CronetDataSourceTest { @Test public void testRequestOpen() throws HttpDataSourceException { mockResponseStartSuccess(); - assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testDataSpec)); - assertConnectionState(CronetDataSource.OPEN_CONNECTION); verify(mockTransferListener).onTransferStart(dataSourceUnderTest, testDataSpec); } - @Test public void testRequestOpenGzippedCompressedReturnsDataSpecLength() throws HttpDataSourceException { @@ -271,7 +265,6 @@ public final class CronetDataSourceTest { testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null); assertEquals(5000 /* contentLength */, dataSourceUnderTest.open(testDataSpec)); - assertConnectionState(CronetDataSource.OPEN_CONNECTION); verify(mockTransferListener).onTransferStart(dataSourceUnderTest, testDataSpec); } @@ -286,7 +279,6 @@ public final class CronetDataSourceTest { // Check for connection not automatically closed. assertFalse(e.getCause() instanceof UnknownHostException); verify(mockUrlRequest, never()).cancel(); - assertConnectionState(CronetDataSource.OPENING_CONNECTION); verify(mockTransferListener, never()).onTransferStart(dataSourceUnderTest, testDataSpec); } } @@ -304,7 +296,6 @@ public final class CronetDataSourceTest { // Check for connection not automatically closed. assertTrue(e.getCause() instanceof UnknownHostException); verify(mockUrlRequest, never()).cancel(); - assertConnectionState(CronetDataSource.OPENING_CONNECTION); verify(mockTransferListener, never()).onTransferStart(dataSourceUnderTest, testDataSpec); } } @@ -321,7 +312,6 @@ public final class CronetDataSourceTest { assertTrue(e instanceof HttpDataSource.InvalidResponseCodeException); // Check for connection not automatically closed. verify(mockUrlRequest, never()).cancel(); - assertConnectionState(CronetDataSource.OPENING_CONNECTION); verify(mockTransferListener, never()).onTransferStart(dataSourceUnderTest, testDataSpec); } } @@ -338,37 +328,16 @@ public final class CronetDataSourceTest { assertTrue(e instanceof HttpDataSource.InvalidContentTypeException); // Check for connection not automatically closed. verify(mockUrlRequest, never()).cancel(); - assertConnectionState(CronetDataSource.OPENING_CONNECTION); verify(mockContentTypePredicate).evaluate(TEST_CONTENT_TYPE); } } - @Test - public void testRequestOpenValidatesContentLength() { - mockResponseStartSuccess(); - - // Data spec's requested length, 5000. Test response's length, 16,000. - testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null); - - try { - dataSourceUnderTest.open(testDataSpec); - fail("HttpDataSource.HttpDataSourceException expected"); - } catch (HttpDataSourceException e) { - verify(mockUrlRequest).addHeader("Range", "bytes=1000-5999"); - // Check for connection not automatically closed. - verify(mockUrlRequest, never()).cancel(); - assertConnectionState(CronetDataSource.OPENING_CONNECTION); - verify(mockTransferListener, never()).onTransferStart(dataSourceUnderTest, testPostDataSpec); - } - } - @Test public void testPostRequestOpen() throws HttpDataSourceException { mockResponseStartSuccess(); dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE); assertEquals(TEST_CONTENT_LENGTH, dataSourceUnderTest.open(testPostDataSpec)); - assertConnectionState(CronetDataSource.OPEN_CONNECTION); verify(mockTransferListener).onTransferStart(dataSourceUnderTest, testPostDataSpec); } @@ -510,7 +479,6 @@ public final class CronetDataSourceTest { dataSourceUnderTest.close(); verify(mockTransferListener).onTransferEnd(dataSourceUnderTest); - assertConnectionState(CronetDataSource.IDLE_CONNECTION); try { bytesRead += dataSourceUnderTest.read(returnedBuffer, 0, 8); @@ -572,7 +540,6 @@ public final class CronetDataSourceTest { verify(mockUrlRequest, times(1)).read(any(ByteBuffer.class)); // Check for connection not automatically closed. verify(mockUrlRequest, never()).cancel(); - assertConnectionState(CronetDataSource.OPEN_CONNECTION); assertEquals(16, bytesRead); } @@ -603,15 +570,12 @@ public final class CronetDataSourceTest { // We should still be trying to open. assertFalse(timedOutCondition.block(50)); - assertEquals(CronetDataSource.OPENING_CONNECTION, dataSourceUnderTest.connectionState); // We should still be trying to open as we approach the timeout. when(mockClock.elapsedRealtime()).thenReturn((long) TEST_CONNECT_TIMEOUT_MS - 1); assertFalse(timedOutCondition.block(50)); - assertEquals(CronetDataSource.OPENING_CONNECTION, dataSourceUnderTest.connectionState); // Now we timeout. when(mockClock.elapsedRealtime()).thenReturn((long) TEST_CONNECT_TIMEOUT_MS); timedOutCondition.block(); - assertEquals(CronetDataSource.OPENING_CONNECTION, dataSourceUnderTest.connectionState); verify(mockTransferListener, never()).onTransferStart(dataSourceUnderTest, testDataSpec); } @@ -637,15 +601,12 @@ public final class CronetDataSourceTest { // We should still be trying to open. assertFalse(openCondition.block(50)); - assertEquals(CronetDataSource.OPENING_CONNECTION, dataSourceUnderTest.connectionState); // We should still be trying to open as we approach the timeout. when(mockClock.elapsedRealtime()).thenReturn((long) TEST_CONNECT_TIMEOUT_MS - 1); assertFalse(openCondition.block(50)); - assertEquals(CronetDataSource.OPENING_CONNECTION, dataSourceUnderTest.connectionState); // The response arrives just in time. dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo); openCondition.block(); - assertEquals(CronetDataSource.OPEN_CONNECTION, dataSourceUnderTest.connectionState); } @Test @@ -674,11 +635,9 @@ public final class CronetDataSourceTest { // We should still be trying to open. assertFalse(timedOutCondition.block(50)); - assertEquals(CronetDataSource.OPENING_CONNECTION, dataSourceUnderTest.connectionState); // We should still be trying to open as we approach the timeout. when(mockClock.elapsedRealtime()).thenReturn((long) TEST_CONNECT_TIMEOUT_MS - 1); assertFalse(timedOutCondition.block(50)); - assertEquals(CronetDataSource.OPENING_CONNECTION, dataSourceUnderTest.connectionState); // A redirect arrives just in time. dataSourceUnderTest.onRedirectReceived(mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl1"); @@ -689,7 +648,6 @@ public final class CronetDataSourceTest { assertFalse(timedOutCondition.block(newTimeoutMs)); // We should still be trying to open as we approach the new timeout. assertFalse(timedOutCondition.block(50)); - assertEquals(CronetDataSource.OPENING_CONNECTION, dataSourceUnderTest.connectionState); // A redirect arrives just in time. dataSourceUnderTest.onRedirectReceived(mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl2"); @@ -700,11 +658,9 @@ public final class CronetDataSourceTest { assertFalse(timedOutCondition.block(newTimeoutMs)); // We should still be trying to open as we approach the new timeout. assertFalse(timedOutCondition.block(50)); - assertEquals(CronetDataSource.OPENING_CONNECTION, dataSourceUnderTest.connectionState); // Now we timeout. when(mockClock.elapsedRealtime()).thenReturn(newTimeoutMs); timedOutCondition.block(); - assertEquals(CronetDataSource.OPENING_CONNECTION, dataSourceUnderTest.connectionState); verify(mockTransferListener, never()).onTransferStart(dataSourceUnderTest, testDataSpec); assertEquals(1, openExceptions.get()); @@ -818,7 +774,7 @@ public final class CronetDataSourceTest { dataSourceUnderTest.onFailed( mockUrlRequest, createUrlResponseInfo(500), // statusCode - null); + mockUrlRequestException); return null; } }).when(mockUrlRequest).read(any(ByteBuffer.class)); @@ -869,8 +825,4 @@ public final class CronetDataSourceTest { return testBuffer; } - private void assertConnectionState(int state) { - assertEquals(state, dataSourceUnderTest.connectionState); - } - } diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index 8e53e32331..a758f71f45 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -37,7 +37,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.chromium.net.CronetEngine; @@ -91,11 +90,6 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou // The size of read buffer passed to cronet UrlRequest.read(). private static final int READ_BUFFER_SIZE_BYTES = 32 * 1024; - /* package */ static final int IDLE_CONNECTION = 5; - /* package */ static final int OPENING_CONNECTION = 2; - /* package */ static final int CONNECTED_CONNECTION = 3; - /* package */ static final int OPEN_CONNECTION = 4; - private final CronetEngine cronetEngine; private final Executor executor; private final Predicate contentTypePredicate; @@ -105,20 +99,29 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou private final boolean resetTimeoutOnRedirects; private final Map requestProperties; private final ConditionVariable operation; - private final ByteBuffer readBuffer; private final Clock clock; + // Accessed by the calling thread only. + private boolean opened; + private long bytesRemaining; + + // Written from the calling thread only. currentUrlRequest.start() calls ensure writes are visible + // to reads made by the Cronet thread. private UrlRequest currentUrlRequest; private DataSpec currentDataSpec; - private UrlResponseInfo responseInfo; - /* package */ volatile int connectionState; + // Reference written and read by calling thread only. Passed to Cronet thread as a local variable. + // operation.open() calls ensure writes into the buffer are visible to reads made by the calling + // thread. + private ByteBuffer readBuffer; + + // Written from the Cronet thread only. operation.open() calls ensure writes are visible to reads + // made by the calling thread. + private UrlResponseInfo responseInfo; + private IOException exception; + private boolean finished; + private volatile long currentConnectTimeoutMs; - private volatile HttpDataSourceException exception; - private volatile long contentLength; - private volatile AtomicLong expectedBytesRemainingToRead; - private volatile boolean hasData; - private volatile boolean responseFinished; /** * @param cronetEngine A CronetEngine. @@ -163,10 +166,8 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou this.readTimeoutMs = readTimeoutMs; this.resetTimeoutOnRedirects = resetTimeoutOnRedirects; this.clock = Assertions.checkNotNull(clock); - readBuffer = ByteBuffer.allocateDirect(READ_BUFFER_SIZE_BYTES); requestProperties = new HashMap<>(); operation = new ConditionVariable(); - connectionState = IDLE_CONNECTION; } // HttpDataSource implementation. @@ -205,10 +206,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou @Override public long open(DataSpec dataSpec) throws HttpDataSourceException { Assertions.checkNotNull(dataSpec); - synchronized (this) { - Assertions.checkState(connectionState == IDLE_CONNECTION, "Connection already open"); - connectionState = OPENING_CONNECTION; - } + Assertions.checkState(!opened); operation.close(); resetConnectTimeout(); @@ -218,61 +216,99 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou boolean requestStarted = blockUntilConnectTimeout(); if (exception != null) { - // An error occurred opening the connection. - throw exception; + throw new OpenException(exception, currentDataSpec, getStatus(currentUrlRequest)); } else if (!requestStarted) { // The timeout was reached before the connection was opened. throw new OpenException(new SocketTimeoutException(), dataSpec, getStatus(currentUrlRequest)); } - // Connection was opened. + // Check for a valid response code. + int responseCode = responseInfo.getHttpStatusCode(); + if (responseCode < 200 || responseCode > 299) { + InvalidResponseCodeException exception = new InvalidResponseCodeException(responseCode, + responseInfo.getAllHeaders(), currentDataSpec); + if (responseCode == 416) { + exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); + } + throw exception; + } + + // Check for a valid content type. + if (contentTypePredicate != null) { + List contentTypeHeaders = responseInfo.getAllHeaders().get(CONTENT_TYPE); + String contentType = isEmpty(contentTypeHeaders) ? null : contentTypeHeaders.get(0); + if (!contentTypePredicate.evaluate(contentType)) { + throw new InvalidContentTypeException(contentType, currentDataSpec); + } + } + + // TODO: Handle the case where we requested a range starting from a non-zero position and + // received a 200 rather than a 206. This occurs if the server does not support partial + // requests, and requires that the source skips to the requested position. + + // Calculate the content length. + if (!getIsCompressed(responseInfo)) { + if (dataSpec.length != C.LENGTH_UNSET) { + bytesRemaining = dataSpec.length; + } else { + bytesRemaining = getContentLength(responseInfo); + } + } else { + // If the response is compressed then the content length will be that of the compressed data + // which isn't what we want. Always use the dataSpec length in this case. + bytesRemaining = currentDataSpec.length; + } + + opened = true; if (listener != null) { listener.onTransferStart(this, dataSpec); } - connectionState = OPEN_CONNECTION; - return contentLength; + + return bytesRemaining; } @Override public int read(byte[] buffer, int offset, int readLength) throws HttpDataSourceException { - synchronized (this) { - Assertions.checkState(connectionState == OPEN_CONNECTION); - } + Assertions.checkState(opened); if (readLength == 0) { return 0; - } - if (expectedBytesRemainingToRead != null && expectedBytesRemainingToRead.get() == 0) { + } else if (bytesRemaining == 0) { return C.RESULT_END_OF_INPUT; } - if (!hasData) { - // Read more data from cronet. + if (readBuffer == null) { + readBuffer = ByteBuffer.allocateDirect(READ_BUFFER_SIZE_BYTES); + readBuffer.limit(0); + } + if (!readBuffer.hasRemaining()) { + // Fill readBuffer with more data from Cronet. operation.close(); readBuffer.clear(); currentUrlRequest.read(readBuffer); if (!operation.block(readTimeoutMs)) { + // We're timing out, but since the operation is still ongoing we'll need to replace + // readBuffer to avoid the possibility of it being written to by this operation during a + // subsequent request. + readBuffer = null; throw new HttpDataSourceException( new SocketTimeoutException(), currentDataSpec, HttpDataSourceException.TYPE_READ); - } - if (exception != null) { - throw exception; - } - // The expected response length is unknown, but cronet has indicated that the request - // already finished successfully. - if (responseFinished) { + } else if (exception != null) { + throw new HttpDataSourceException(exception, currentDataSpec, + HttpDataSourceException.TYPE_READ); + } else if (finished) { return C.RESULT_END_OF_INPUT; + } else { + // The operation didn't time out, fail or finish, and therefore data must have been read. + readBuffer.flip(); } } int bytesRead = Math.min(readBuffer.remaining(), readLength); readBuffer.get(buffer, offset, bytesRead); - if (!readBuffer.hasRemaining()) { - hasData = false; - } - if (expectedBytesRemainingToRead != null) { - expectedBytesRemainingToRead.addAndGet(-bytesRead); + if (bytesRemaining != C.LENGTH_UNSET) { + bytesRemaining -= bytesRead; } if (listener != null) { listener.onBytesTransferred(this, bytesRead); @@ -286,26 +322,26 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou currentUrlRequest.cancel(); currentUrlRequest = null; } + if (readBuffer != null) { + readBuffer.limit(0); + } currentDataSpec = null; - exception = null; - contentLength = 0; - hasData = false; responseInfo = null; - expectedBytesRemainingToRead = null; - responseFinished = false; - try { - if (listener != null && connectionState == OPEN_CONNECTION) { + exception = null; + finished = false; + if (opened) { + opened = false; + if (listener != null) { listener.onTransferEnd(this); } - } finally { - connectionState = IDLE_CONNECTION; } } // UrlRequest.Callback implementation @Override - public void onRedirectReceived(UrlRequest request, UrlResponseInfo info, String newLocationUrl) { + public synchronized void onRedirectReceived(UrlRequest request, UrlResponseInfo info, + String newLocationUrl) { if (request != currentUrlRequest) { return; } @@ -315,8 +351,8 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou // For other redirect response codes the POST request is converted to a GET request and the // redirect is followed. if (responseCode == 307 || responseCode == 308) { - exception = new OpenException("POST request redirected with 307 or 308 response code", - currentDataSpec, getStatus(request)); + exception = new InvalidResponseCodeException(responseCode, info.getAllHeaders(), + currentDataSpec); operation.open(); return; } @@ -332,51 +368,8 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou if (request != currentUrlRequest) { return; } - try { - // Check for a valid response code. - int responseCode = info.getHttpStatusCode(); - if (responseCode < 200 || responseCode > 299) { - InvalidResponseCodeException exception = new InvalidResponseCodeException( - responseCode, info.getAllHeaders(), currentDataSpec); - if (responseCode == 416) { - exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); - } - throw exception; - } - // Check for a valid content type. - if (contentTypePredicate != null) { - List contentTypeHeaders = info.getAllHeaders().get(CONTENT_TYPE); - String contentType = contentTypeHeaders == null || contentTypeHeaders.isEmpty() ? null - : contentTypeHeaders.get(0); - if (!contentTypePredicate.evaluate(contentType)) { - throw new InvalidContentTypeException(contentType, currentDataSpec); - } - } - - responseInfo = info; - if (getIsCompressed(info)) { - contentLength = currentDataSpec.length; - } else { - // Check content length. - contentLength = getContentLength(info); - // If a specific length is requested and a specific length is returned but the 2 don't match - // it's an error. - if (currentDataSpec.length != C.LENGTH_UNSET && contentLength != C.LENGTH_UNSET - && currentDataSpec.length != contentLength) { - throw new OpenException("Content length did not match requested length", currentDataSpec, - getStatus(request)); - } - } - if (contentLength > 0) { - expectedBytesRemainingToRead = new AtomicLong(contentLength); - } - - connectionState = CONNECTED_CONNECTION; - } catch (HttpDataSourceException e) { - exception = e; - } finally { - operation.open(); - } + responseInfo = info; + operation.open(); } @Override @@ -385,17 +378,15 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou if (request != currentUrlRequest) { return; } - readBuffer.flip(); - hasData = true; operation.open(); } @Override - public void onSucceeded(UrlRequest request, UrlResponseInfo info) { + public synchronized void onSucceeded(UrlRequest request, UrlResponseInfo info) { if (request != currentUrlRequest) { return; } - responseFinished = true; + finished = true; operation.open(); } @@ -405,14 +396,8 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou if (request != currentUrlRequest) { return; } - if (connectionState == OPENING_CONNECTION) { - IOException cause = error.getErrorCode() == UrlRequestException.ERROR_HOSTNAME_NOT_RESOLVED - ? new UnknownHostException() : error; - exception = new OpenException(cause, currentDataSpec, getStatus(request)); - } else if (connectionState == OPEN_CONNECTION) { - exception = new HttpDataSourceException(error, currentDataSpec, - HttpDataSourceException.TYPE_READ); - } + exception = error.getErrorCode() == UrlRequestException.ERROR_HOSTNAME_NOT_RESOLVED + ? new UnknownHostException() : error; operation.open(); } @@ -477,7 +462,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou Map> headers = info.getAllHeaders(); List contentLengthHeaders = headers.get("Content-Length"); String contentLengthHeader = null; - if (contentLengthHeaders != null && !contentLengthHeaders.isEmpty()) { + if (!isEmpty(contentLengthHeaders)) { contentLengthHeader = contentLengthHeaders.get(0); if (!TextUtils.isEmpty(contentLengthHeader)) { try { @@ -488,7 +473,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou } } List contentRangeHeaders = headers.get("Content-Range"); - if (contentRangeHeaders != null && !contentRangeHeaders.isEmpty()) { + if (!isEmpty(contentRangeHeaders)) { String contentRangeHeader = contentRangeHeaders.get(0); Matcher matcher = CONTENT_RANGE_HEADER_PATTERN.matcher(contentRangeHeader); if (matcher.find()) { @@ -530,4 +515,8 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou return statusHolder[0]; } + private static boolean isEmpty(List list) { + return list == null || list.isEmpty(); + } + } diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java index 1cffee8188..0f94dad158 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSourceFactory.java @@ -41,7 +41,7 @@ public final class CronetDataSourceFactory implements Factory { private final CronetEngine cronetEngine; private final Executor executor; private final Predicate contentTypePredicate; - private final TransferListener transferListener; + private final TransferListener transferListener; private final int connectTimeoutMs; private final int readTimeoutMs; private final boolean resetTimeoutOnRedirects; diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java index 7a8f3bca3f..2b6eaa736d 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java @@ -185,9 +185,12 @@ public class OkHttpDataSource implements HttpDataSource { bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0; // Determine the length of the data to be read, after skipping. - long contentLength = response.body().contentLength(); - bytesToRead = dataSpec.length != C.LENGTH_UNSET ? dataSpec.length - : (contentLength != -1 ? (contentLength - bytesToSkip) : C.LENGTH_UNSET); + if (dataSpec.length != C.LENGTH_UNSET) { + bytesToRead = dataSpec.length; + } else { + long contentLength = response.body().contentLength(); + bytesToRead = contentLength != -1 ? (contentLength - bytesToSkip) : C.LENGTH_UNSET; + } opened = true; if (listener != null) { diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index b193b446c2..34ec76b673 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -231,10 +231,13 @@ public class DefaultHttpDataSource implements HttpDataSource { // Determine the length of the data to be read, after skipping. if ((dataSpec.flags & DataSpec.FLAG_ALLOW_GZIP) == 0) { - long contentLength = getContentLength(connection); - bytesToRead = dataSpec.length != C.LENGTH_UNSET ? dataSpec.length - : contentLength != C.LENGTH_UNSET ? contentLength - bytesToSkip - : C.LENGTH_UNSET; + if (dataSpec.length != C.LENGTH_UNSET) { + bytesToRead = dataSpec.length; + } else { + long contentLength = getContentLength(connection); + bytesToRead = contentLength != C.LENGTH_UNSET ? (contentLength - bytesToSkip) + : C.LENGTH_UNSET; + } } 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