Commit 985160a4 by tonihei Committed by Oliver Woodman

Use BaseDataSource for all internal leaf data sources.

This allows all leaf data sources (i.e. ones which do not forward the requests
to other data sources) to accept multiple listeners.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=203097587
parent e48b712a
......@@ -16,10 +16,13 @@
package com.google.android.exoplayer2.ext.cronet;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.upstream.BaseDataSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSourceException;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource;
......@@ -48,9 +51,10 @@ import org.chromium.net.UrlResponseInfo;
/**
* DataSource without intermediate buffer based on Cronet API set using UrlRequest.
*
* <p>This class's methods are organized in the sequence of expected calls.
*/
public class CronetDataSource extends UrlRequest.Callback implements HttpDataSource {
public class CronetDataSource extends BaseDataSource implements HttpDataSource {
/**
* Thrown when an error is encountered when trying to open a {@link CronetDataSource}.
......@@ -96,6 +100,8 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
*/
public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
/* package */ final UrlRequest.Callback urlRequestCallback;
private static final String TAG = "CronetDataSource";
private static final String CONTENT_TYPE = "Content-Type";
private static final String SET_COOKIE = "Set-Cookie";
......@@ -109,7 +115,6 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
private final CronetEngine cronetEngine;
private final Executor executor;
private final Predicate<String> contentTypePredicate;
private final TransferListener<? super CronetDataSource> listener;
private final int connectTimeoutMs;
private final int readTimeoutMs;
private final boolean resetTimeoutOnRedirects;
......@@ -144,41 +149,49 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
/**
* @param cronetEngine A CronetEngine.
* @param executor The {@link java.util.concurrent.Executor} that will handle responses.
* This may be a direct executor (i.e. executes tasks on the calling thread) in order
* to avoid a thread hop from Cronet's internal network thread to the response handling
* thread. However, to avoid slowing down overall network performance, care must be taken
* to make sure response handling is a fast operation when using a direct executor.
* @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may
* be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread
* hop from Cronet's internal network thread to the response handling thread. However, to
* avoid slowing down overall network performance, care must be taken to make sure response
* handling is a fast operation when using a direct executor.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then an {@link InvalidContentTypeException} is thrown from
* {@link #open(DataSpec)}.
* predicate then an {@link InvalidContentTypeException} is thrown from {@link
* #open(DataSpec)}.
* @param listener An optional listener.
*/
public CronetDataSource(CronetEngine cronetEngine, Executor executor,
Predicate<String> contentTypePredicate, TransferListener<? super CronetDataSource> listener) {
public CronetDataSource(
CronetEngine cronetEngine,
Executor executor,
Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super DataSource> listener) {
this(cronetEngine, executor, contentTypePredicate, listener, DEFAULT_CONNECT_TIMEOUT_MILLIS,
DEFAULT_READ_TIMEOUT_MILLIS, false, null, false);
}
/**
* @param cronetEngine A CronetEngine.
* @param executor The {@link java.util.concurrent.Executor} that will handle responses.
* This may be a direct executor (i.e. executes tasks on the calling thread) in order
* to avoid a thread hop from Cronet's internal network thread to the response handling
* thread. However, to avoid slowing down overall network performance, care must be taken
* to make sure response handling is a fast operation when using a direct executor.
* @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may
* be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread
* hop from Cronet's internal network thread to the response handling thread. However, to
* avoid slowing down overall network performance, care must be taken to make sure response
* handling is a fast operation when using a direct executor.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then an {@link InvalidContentTypeException} is thrown from
* {@link #open(DataSpec)}.
* predicate then an {@link InvalidContentTypeException} is thrown from {@link
* #open(DataSpec)}.
* @param listener An optional listener.
* @param connectTimeoutMs The connection timeout, in milliseconds.
* @param readTimeoutMs The read timeout, in milliseconds.
* @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs.
* @param defaultRequestProperties The default request properties to be used.
*/
public CronetDataSource(CronetEngine cronetEngine, Executor executor,
Predicate<String> contentTypePredicate, TransferListener<? super CronetDataSource> listener,
int connectTimeoutMs, int readTimeoutMs, boolean resetTimeoutOnRedirects,
public CronetDataSource(
CronetEngine cronetEngine,
Executor executor,
Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super DataSource> listener,
int connectTimeoutMs,
int readTimeoutMs,
boolean resetTimeoutOnRedirects,
RequestProperties defaultRequestProperties) {
this(cronetEngine, executor, contentTypePredicate, listener, connectTimeoutMs,
readTimeoutMs, resetTimeoutOnRedirects, Clock.DEFAULT, defaultRequestProperties, false);
......@@ -186,14 +199,14 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
/**
* @param cronetEngine A CronetEngine.
* @param executor The {@link java.util.concurrent.Executor} that will handle responses.
* This may be a direct executor (i.e. executes tasks on the calling thread) in order
* to avoid a thread hop from Cronet's internal network thread to the response handling
* thread. However, to avoid slowing down overall network performance, care must be taken
* to make sure response handling is a fast operation when using a direct executor.
* @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may
* be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread
* hop from Cronet's internal network thread to the response handling thread. However, to
* avoid slowing down overall network performance, care must be taken to make sure response
* handling is a fast operation when using a direct executor.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then an {@link InvalidContentTypeException} is thrown from
* {@link #open(DataSpec)}.
* predicate then an {@link InvalidContentTypeException} is thrown from {@link
* #open(DataSpec)}.
* @param listener An optional listener.
* @param connectTimeoutMs The connection timeout, in milliseconds.
* @param readTimeoutMs The read timeout, in milliseconds.
......@@ -202,23 +215,37 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
* @param handleSetCookieRequests Whether "Set-Cookie" requests on redirect should be forwarded to
* the redirect url in the "Cookie" header.
*/
public CronetDataSource(CronetEngine cronetEngine, Executor executor,
Predicate<String> contentTypePredicate, TransferListener<? super CronetDataSource> listener,
int connectTimeoutMs, int readTimeoutMs, boolean resetTimeoutOnRedirects,
RequestProperties defaultRequestProperties, boolean handleSetCookieRequests) {
public CronetDataSource(
CronetEngine cronetEngine,
Executor executor,
Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super DataSource> listener,
int connectTimeoutMs,
int readTimeoutMs,
boolean resetTimeoutOnRedirects,
RequestProperties defaultRequestProperties,
boolean handleSetCookieRequests) {
this(cronetEngine, executor, contentTypePredicate, listener, connectTimeoutMs,
readTimeoutMs, resetTimeoutOnRedirects, Clock.DEFAULT, defaultRequestProperties,
handleSetCookieRequests);
}
/* package */ CronetDataSource(CronetEngine cronetEngine, Executor executor,
Predicate<String> contentTypePredicate, TransferListener<? super CronetDataSource> listener,
int connectTimeoutMs, int readTimeoutMs, boolean resetTimeoutOnRedirects, Clock clock,
RequestProperties defaultRequestProperties, boolean handleSetCookieRequests) {
/* package */ CronetDataSource(
CronetEngine cronetEngine,
Executor executor,
Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super DataSource> listener,
int connectTimeoutMs,
int readTimeoutMs,
boolean resetTimeoutOnRedirects,
Clock clock,
RequestProperties defaultRequestProperties,
boolean handleSetCookieRequests) {
super(DataSource.TYPE_REMOTE);
this.urlRequestCallback = new UrlRequestCallback();
this.cronetEngine = Assertions.checkNotNull(cronetEngine);
this.executor = Assertions.checkNotNull(executor);
this.contentTypePredicate = contentTypePredicate;
this.listener = listener;
this.connectTimeoutMs = connectTimeoutMs;
this.readTimeoutMs = readTimeoutMs;
this.resetTimeoutOnRedirects = resetTimeoutOnRedirects;
......@@ -227,6 +254,9 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
this.handleSetCookieRequests = handleSetCookieRequests;
requestProperties = new RequestProperties();
operation = new ConditionVariable();
if (listener != null) {
addTransferListener(listener);
}
}
// HttpDataSource implementation.
......@@ -324,9 +354,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
}
opened = true;
if (listener != null) {
listener.onTransferStart(this, dataSpec);
}
transferStarted(dataSpec);
return bytesRemaining;
}
......@@ -392,9 +420,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead;
}
if (listener != null) {
listener.onBytesTransferred(this, bytesRead);
}
bytesTransferred(bytesRead);
return bytesRead;
}
......@@ -413,107 +439,17 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
finished = false;
if (opened) {
opened = false;
if (listener != null) {
listener.onTransferEnd(this);
}
}
}
// UrlRequest.Callback implementation
@Override
public synchronized void onRedirectReceived(UrlRequest request, UrlResponseInfo info,
String newLocationUrl) {
if (request != currentUrlRequest) {
return;
}
if (currentDataSpec.postBody != null) {
int responseCode = info.getHttpStatusCode();
// The industry standard is to disregard POST redirects when the status code is 307 or 308.
// 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 InvalidResponseCodeException(responseCode, info.getAllHeaders(),
currentDataSpec);
operation.open();
return;
}
}
if (resetTimeoutOnRedirects) {
resetConnectTimeout();
}
Map<String, List<String>> headers = info.getAllHeaders();
if (!handleSetCookieRequests || isEmpty(headers.get(SET_COOKIE))) {
request.followRedirect();
} else {
currentUrlRequest.cancel();
DataSpec redirectUrlDataSpec = new DataSpec(Uri.parse(newLocationUrl),
currentDataSpec.postBody, currentDataSpec.absoluteStreamPosition,
currentDataSpec.position, currentDataSpec.length, currentDataSpec.key,
currentDataSpec.flags);
UrlRequest.Builder requestBuilder;
try {
requestBuilder = buildRequestBuilder(redirectUrlDataSpec);
} catch (IOException e) {
exception = e;
return;
}
String cookieHeadersValue = parseCookies(headers.get(SET_COOKIE));
attachCookies(requestBuilder, cookieHeadersValue);
currentUrlRequest = requestBuilder.build();
currentUrlRequest.start();
}
}
@Override
public synchronized void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
if (request != currentUrlRequest) {
return;
}
responseInfo = info;
operation.open();
}
@Override
public synchronized void onReadCompleted(UrlRequest request, UrlResponseInfo info,
ByteBuffer buffer) {
if (request != currentUrlRequest) {
return;
}
operation.open();
}
@Override
public synchronized void onSucceeded(UrlRequest request, UrlResponseInfo info) {
if (request != currentUrlRequest) {
return;
}
finished = true;
operation.open();
}
@Override
public synchronized void onFailed(UrlRequest request, UrlResponseInfo info,
CronetException error) {
if (request != currentUrlRequest) {
return;
transferEnded();
}
if (error instanceof NetworkException
&& ((NetworkException) error).getErrorCode()
== NetworkException.ERROR_HOSTNAME_NOT_RESOLVED) {
exception = new UnknownHostException();
} else {
exception = error;
}
operation.open();
}
// Internal methods.
private UrlRequest.Builder buildRequestBuilder(DataSpec dataSpec) throws IOException {
UrlRequest.Builder requestBuilder = cronetEngine.newUrlRequestBuilder(
dataSpec.uri.toString(), this, executor).allowDirectExecutor();
UrlRequest.Builder requestBuilder =
cronetEngine
.newUrlRequestBuilder(dataSpec.uri.toString(), urlRequestCallback, executor)
.allowDirectExecutor();
// Set the headers.
boolean isContentTypeHeaderSet = false;
if (defaultRequestProperties != null) {
......@@ -656,4 +592,99 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
return list == null || list.isEmpty();
}
private final class UrlRequestCallback extends UrlRequest.Callback {
@Override
public synchronized void onRedirectReceived(
UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
if (request != currentUrlRequest) {
return;
}
if (currentDataSpec.postBody != null) {
int responseCode = info.getHttpStatusCode();
// The industry standard is to disregard POST redirects when the status code is 307 or 308.
// 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 InvalidResponseCodeException(responseCode, info.getAllHeaders(), currentDataSpec);
operation.open();
return;
}
}
if (resetTimeoutOnRedirects) {
resetConnectTimeout();
}
Map<String, List<String>> headers = info.getAllHeaders();
if (!handleSetCookieRequests || isEmpty(headers.get(SET_COOKIE))) {
request.followRedirect();
} else {
currentUrlRequest.cancel();
DataSpec redirectUrlDataSpec =
new DataSpec(
Uri.parse(newLocationUrl),
currentDataSpec.postBody,
currentDataSpec.absoluteStreamPosition,
currentDataSpec.position,
currentDataSpec.length,
currentDataSpec.key,
currentDataSpec.flags);
UrlRequest.Builder requestBuilder;
try {
requestBuilder = buildRequestBuilder(redirectUrlDataSpec);
} catch (IOException e) {
exception = e;
return;
}
String cookieHeadersValue = parseCookies(headers.get(SET_COOKIE));
attachCookies(requestBuilder, cookieHeadersValue);
currentUrlRequest = requestBuilder.build();
currentUrlRequest.start();
}
}
@Override
public synchronized void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
if (request != currentUrlRequest) {
return;
}
responseInfo = info;
operation.open();
}
@Override
public synchronized void onReadCompleted(
UrlRequest request, UrlResponseInfo info, ByteBuffer buffer) {
if (request != currentUrlRequest) {
return;
}
operation.open();
}
@Override
public synchronized void onSucceeded(UrlRequest request, UrlResponseInfo info) {
if (request != currentUrlRequest) {
return;
}
finished = true;
operation.open();
}
@Override
public synchronized void onFailed(
UrlRequest request, UrlResponseInfo info, CronetException error) {
if (request != currentUrlRequest) {
return;
}
if (error instanceof NetworkException
&& ((NetworkException) error).getErrorCode()
== NetworkException.ERROR_HOSTNAME_NOT_RESOLVED) {
exception = new UnknownHostException();
} else {
exception = error;
}
operation.open();
}
}
}
......@@ -24,7 +24,6 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
......@@ -33,6 +32,7 @@ import android.net.Uri;
import android.os.ConditionVariable;
import android.os.SystemClock;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource.HttpDataSourceException;
......@@ -87,7 +87,7 @@ public final class CronetDataSourceTest {
@Mock private UrlRequest.Builder mockUrlRequestBuilder;
@Mock private UrlRequest mockUrlRequest;
@Mock private Predicate<String> mockContentTypePredicate;
@Mock private TransferListener<CronetDataSource> mockTransferListener;
@Mock private TransferListener<DataSource> mockTransferListener;
@Mock private Executor mockExecutor;
@Mock private NetworkException mockNetworkException;
@Mock private CronetEngine mockCronetEngine;
......@@ -99,18 +99,17 @@ public final class CronetDataSourceTest {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
dataSourceUnderTest =
spy(
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
Clock.DEFAULT,
null,
false));
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
Clock.DEFAULT,
null,
false);
when(mockContentTypePredicate.evaluate(anyString())).thenReturn(true);
when(mockCronetEngine.newUrlRequestBuilder(
anyString(), any(UrlRequest.Callback.class), any(Executor.class)))
......@@ -172,9 +171,10 @@ public final class CronetDataSourceTest {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
// Invoke the callback for the previous request.
dataSourceUnderTest.onFailed(
dataSourceUnderTest.urlRequestCallback.onFailed(
mockUrlRequest, testUrlResponseInfo, mockNetworkException);
dataSourceUnderTest.onResponseStarted(mockUrlRequest2, testUrlResponseInfo);
dataSourceUnderTest.urlRequestCallback.onResponseStarted(
mockUrlRequest2, testUrlResponseInfo);
return null;
}
})
......@@ -601,7 +601,7 @@ public final class CronetDataSourceTest {
}
@Test
public void testConnectResponseBeforeTimeout() throws InterruptedException {
public void testConnectResponseBeforeTimeout() throws Exception {
long startTimeMs = SystemClock.elapsedRealtime();
final ConditionVariable startCondition = buildUrlRequestStartedCondition();
final CountDownLatch openLatch = new CountDownLatch(1);
......@@ -625,12 +625,12 @@ public final class CronetDataSourceTest {
ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
assertNotCountedDown(openLatch);
// The response arrives just in time.
dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo);
dataSourceUnderTest.urlRequestCallback.onResponseStarted(mockUrlRequest, testUrlResponseInfo);
openLatch.await();
}
@Test
public void testRedirectIncreasesConnectionTimeout() throws InterruptedException {
public void testRedirectIncreasesConnectionTimeout() throws Exception {
long startTimeMs = SystemClock.elapsedRealtime();
final ConditionVariable startCondition = buildUrlRequestStartedCondition();
final CountDownLatch timedOutLatch = new CountDownLatch(1);
......@@ -659,7 +659,7 @@ public final class CronetDataSourceTest {
ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
assertNotCountedDown(timedOutLatch);
// A redirect arrives just in time.
dataSourceUnderTest.onRedirectReceived(
dataSourceUnderTest.urlRequestCallback.onRedirectReceived(
mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl1");
long newTimeoutMs = 2 * TEST_CONNECT_TIMEOUT_MS - 1;
......@@ -667,7 +667,7 @@ public final class CronetDataSourceTest {
// We should still be trying to open as we approach the new timeout.
assertNotCountedDown(timedOutLatch);
// A redirect arrives just in time.
dataSourceUnderTest.onRedirectReceived(
dataSourceUnderTest.urlRequestCallback.onRedirectReceived(
mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl2");
newTimeoutMs = 3 * TEST_CONNECT_TIMEOUT_MS - 2;
......@@ -700,18 +700,17 @@ public final class CronetDataSourceTest {
testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeaders()
throws HttpDataSourceException {
dataSourceUnderTest =
spy(
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
Clock.DEFAULT,
null,
true));
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
Clock.DEFAULT,
null,
true);
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
mockSingleRedirectSuccess();
......@@ -732,18 +731,17 @@ public final class CronetDataSourceTest {
throws HttpDataSourceException {
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
dataSourceUnderTest =
spy(
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
Clock.DEFAULT,
null,
true));
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
Clock.DEFAULT,
null,
true);
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
mockSingleRedirectSuccess();
......@@ -772,18 +770,17 @@ public final class CronetDataSourceTest {
public void testRedirectNoSetCookieFollowsRedirect_dataSourceHandlesSetCookie()
throws HttpDataSourceException {
dataSourceUnderTest =
spy(
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
Clock.DEFAULT,
null,
true));
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
Clock.DEFAULT,
null,
true);
mockSingleRedirectSuccess();
mockFollowRedirectSuccess();
......@@ -889,7 +886,8 @@ public final class CronetDataSourceTest {
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo);
dataSourceUnderTest.urlRequestCallback.onResponseStarted(
mockUrlRequest, testUrlResponseInfo);
return null;
}
})
......@@ -902,7 +900,7 @@ public final class CronetDataSourceTest {
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onRedirectReceived(
dataSourceUnderTest.urlRequestCallback.onRedirectReceived(
mockUrlRequest,
createUrlResponseInfo(307), // statusCode
"http://redirect.location.com");
......@@ -920,12 +918,13 @@ public final class CronetDataSourceTest {
public Object answer(InvocationOnMock invocation) throws Throwable {
if (!redirectCalled) {
redirectCalled = true;
dataSourceUnderTest.onRedirectReceived(
dataSourceUnderTest.urlRequestCallback.onRedirectReceived(
mockUrlRequest,
createUrlResponseInfoWithUrl("http://example.com/video", 300),
"http://example.com/video/redirect");
} else {
dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo);
dataSourceUnderTest.urlRequestCallback.onResponseStarted(
mockUrlRequest, testUrlResponseInfo);
}
return null;
}
......@@ -939,7 +938,8 @@ public final class CronetDataSourceTest {
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo);
dataSourceUnderTest.urlRequestCallback.onResponseStarted(
mockUrlRequest, testUrlResponseInfo);
return null;
}
})
......@@ -952,7 +952,7 @@ public final class CronetDataSourceTest {
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onFailed(
dataSourceUnderTest.urlRequestCallback.onFailed(
mockUrlRequest,
createUrlResponseInfo(500), // statusCode
mockNetworkException);
......@@ -970,14 +970,15 @@ public final class CronetDataSourceTest {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
if (positionAndRemaining[1] == 0) {
dataSourceUnderTest.onSucceeded(mockUrlRequest, testUrlResponseInfo);
dataSourceUnderTest.urlRequestCallback.onSucceeded(
mockUrlRequest, testUrlResponseInfo);
} else {
ByteBuffer inputBuffer = (ByteBuffer) invocation.getArguments()[0];
int readLength = Math.min(positionAndRemaining[1], inputBuffer.remaining());
inputBuffer.put(buildTestDataBuffer(positionAndRemaining[0], readLength));
positionAndRemaining[0] += readLength;
positionAndRemaining[1] -= readLength;
dataSourceUnderTest.onReadCompleted(
dataSourceUnderTest.urlRequestCallback.onReadCompleted(
mockUrlRequest, testUrlResponseInfo, inputBuffer);
}
return null;
......@@ -992,7 +993,7 @@ public final class CronetDataSourceTest {
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onFailed(
dataSourceUnderTest.urlRequestCallback.onFailed(
mockUrlRequest,
createUrlResponseInfo(500), // statusCode
mockNetworkException);
......
......@@ -20,6 +20,8 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.upstream.BaseDataSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSourceException;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource;
......@@ -42,10 +44,8 @@ import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* An {@link HttpDataSource} that delegates to Square's {@link Call.Factory}.
*/
public class OkHttpDataSource implements HttpDataSource {
/** An {@link HttpDataSource} that delegates to Square's {@link Call.Factory}. */
public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
static {
ExoPlayerLibraryInfo.registerModule("goog.exo.okhttp");
......@@ -58,7 +58,6 @@ public class OkHttpDataSource implements HttpDataSource {
@Nullable private final String userAgent;
@Nullable private final Predicate<String> contentTypePredicate;
@Nullable private final TransferListener<? super OkHttpDataSource> listener;
@Nullable private final CacheControl cacheControl;
@Nullable private final RequestProperties defaultRequestProperties;
......@@ -90,13 +89,15 @@ public class OkHttpDataSource implements HttpDataSource {
* by the source.
* @param userAgent An optional User-Agent string.
* @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)}.
* predicate then a {@link InvalidContentTypeException} is thrown from {@link
* #open(DataSpec)}.
* @param listener An optional listener.
*/
public OkHttpDataSource(@NonNull Call.Factory callFactory, @Nullable String userAgent,
public OkHttpDataSource(
@NonNull Call.Factory callFactory,
@Nullable String userAgent,
@Nullable Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super OkHttpDataSource> listener) {
@Nullable TransferListener<? super DataSource> listener) {
this(callFactory, userAgent, contentTypePredicate, listener, null, null);
}
......@@ -105,24 +106,30 @@ public class OkHttpDataSource implements HttpDataSource {
* by the source.
* @param userAgent An optional User-Agent string.
* @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)}.
* predicate then a {@link InvalidContentTypeException} is thrown from {@link
* #open(DataSpec)}.
* @param listener An optional listener.
* @param cacheControl An optional {@link CacheControl} for setting the Cache-Control header.
* @param defaultRequestProperties The optional default {@link RequestProperties} to be sent to
* the server as HTTP headers on every request.
* the server as HTTP headers on every request.
*/
public OkHttpDataSource(@NonNull Call.Factory callFactory, @Nullable String userAgent,
public OkHttpDataSource(
@NonNull Call.Factory callFactory,
@Nullable String userAgent,
@Nullable Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super OkHttpDataSource> listener,
@Nullable CacheControl cacheControl, @Nullable RequestProperties defaultRequestProperties) {
@Nullable TransferListener<? super DataSource> listener,
@Nullable CacheControl cacheControl,
@Nullable RequestProperties defaultRequestProperties) {
super(DataSource.TYPE_REMOTE);
this.callFactory = Assertions.checkNotNull(callFactory);
this.userAgent = userAgent;
this.contentTypePredicate = contentTypePredicate;
this.listener = listener;
this.cacheControl = cacheControl;
this.defaultRequestProperties = defaultRequestProperties;
this.requestProperties = new RequestProperties();
if (listener != null) {
addTransferListener(listener);
}
}
@Override
......@@ -203,9 +210,7 @@ public class OkHttpDataSource implements HttpDataSource {
}
opened = true;
if (listener != null) {
listener.onTransferStart(this, dataSpec);
}
transferStarted(dataSpec);
return bytesToRead;
}
......@@ -224,9 +229,7 @@ public class OkHttpDataSource implements HttpDataSource {
public void close() throws HttpDataSourceException {
if (opened) {
opened = false;
if (listener != null) {
listener.onTransferEnd(this);
}
transferEnded();
closeConnectionQuietly();
}
}
......@@ -333,9 +336,7 @@ public class OkHttpDataSource implements HttpDataSource {
throw new EOFException();
}
bytesSkipped += read;
if (listener != null) {
listener.onBytesTransferred(this, read);
}
bytesTransferred(read);
}
// Release the shared skip buffer.
......@@ -378,9 +379,7 @@ public class OkHttpDataSource implements HttpDataSource {
}
bytesRead += read;
if (listener != null) {
listener.onBytesTransferred(this, read);
}
bytesTransferred(read);
return read;
}
......
......@@ -19,6 +19,7 @@ import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.upstream.BaseDataSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener;
......@@ -26,17 +27,13 @@ import java.io.IOException;
import net.butterflytv.rtmp_client.RtmpClient;
import net.butterflytv.rtmp_client.RtmpClient.RtmpIOException;
/**
* A Real-Time Messaging Protocol (RTMP) {@link DataSource}.
*/
public final class RtmpDataSource implements DataSource {
/** A Real-Time Messaging Protocol (RTMP) {@link DataSource}. */
public final class RtmpDataSource extends BaseDataSource {
static {
ExoPlayerLibraryInfo.registerModule("goog.exo.rtmp");
}
@Nullable private final TransferListener<? super RtmpDataSource> listener;
private RtmpClient rtmpClient;
private Uri uri;
......@@ -44,11 +41,12 @@ public final class RtmpDataSource implements DataSource {
this(null);
}
/**
* @param listener An optional listener.
*/
public RtmpDataSource(@Nullable TransferListener<? super RtmpDataSource> listener) {
this.listener = listener;
/** @param listener An optional listener. */
public RtmpDataSource(@Nullable TransferListener<? super DataSource> listener) {
super(DataSource.TYPE_REMOTE);
if (listener != null) {
addTransferListener(listener);
}
}
@Override
......@@ -57,9 +55,7 @@ public final class RtmpDataSource implements DataSource {
rtmpClient.open(dataSpec.uri.toString(), false);
this.uri = dataSpec.uri;
if (listener != null) {
listener.onTransferStart(this, dataSpec);
}
transferStarted(dataSpec);
return C.LENGTH_UNSET;
}
......@@ -69,9 +65,7 @@ public final class RtmpDataSource implements DataSource {
if (bytesRead == -1) {
return C.RESULT_END_OF_INPUT;
}
if (listener != null) {
listener.onBytesTransferred(this, bytesRead);
}
bytesTransferred(bytesRead);
return bytesRead;
}
......@@ -79,9 +73,7 @@ public final class RtmpDataSource implements DataSource {
public void close() {
if (uri != null) {
uri = null;
if (listener != null) {
listener.onTransferEnd(this);
}
transferEnded();
}
if (rtmpClient != null) {
rtmpClient.close();
......
......@@ -25,17 +25,14 @@ import com.google.android.exoplayer2.upstream.TransferListener;
*/
public final class RtmpDataSourceFactory implements DataSource.Factory {
@Nullable
private final TransferListener<? super RtmpDataSource> listener;
private final @Nullable TransferListener<? super DataSource> listener;
public RtmpDataSourceFactory() {
this(null);
}
/**
* @param listener An optional listener.
*/
public RtmpDataSourceFactory(@Nullable TransferListener<? super RtmpDataSource> listener) {
/** @param listener An optional listener. */
public RtmpDataSourceFactory(@Nullable TransferListener<? super DataSource> listener) {
this.listener = listener;
}
......
......@@ -18,15 +18,14 @@ package com.google.android.exoplayer2.upstream;
import android.content.Context;
import android.content.res.AssetManager;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
/**
* A {@link DataSource} for reading from a local asset.
*/
public final class AssetDataSource implements DataSource {
/** A {@link DataSource} for reading from a local asset. */
public final class AssetDataSource extends BaseDataSource {
/**
* Thrown when an {@link IOException} is encountered reading a local asset.
......@@ -40,10 +39,9 @@ public final class AssetDataSource implements DataSource {
}
private final AssetManager assetManager;
private final TransferListener<? super AssetDataSource> listener;
private Uri uri;
private InputStream inputStream;
private @Nullable Uri uri;
private @Nullable InputStream inputStream;
private long bytesRemaining;
private boolean opened;
......@@ -58,9 +56,12 @@ public final class AssetDataSource implements DataSource {
* @param context A context.
* @param listener An optional listener.
*/
public AssetDataSource(Context context, TransferListener<? super AssetDataSource> listener) {
public AssetDataSource(Context context, @Nullable TransferListener<? super DataSource> listener) {
super(DataSource.TYPE_LOCAL);
this.assetManager = context.getAssets();
this.listener = listener;
if (listener != null) {
addTransferListener(listener);
}
}
@Override
......@@ -96,9 +97,7 @@ public final class AssetDataSource implements DataSource {
}
opened = true;
if (listener != null) {
listener.onTransferStart(this, dataSpec);
}
transferStarted(dataSpec);
return bytesRemaining;
}
......@@ -129,14 +128,12 @@ public final class AssetDataSource implements DataSource {
if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead;
}
if (listener != null) {
listener.onBytesTransferred(this, bytesRead);
}
bytesTransferred(bytesRead);
return bytesRead;
}
@Override
public Uri getUri() {
public @Nullable Uri getUri() {
return uri;
}
......@@ -153,9 +150,7 @@ public final class AssetDataSource implements DataSource {
inputStream = null;
if (opened) {
opened = false;
if (listener != null) {
listener.onTransferEnd(this);
}
transferEnded();
}
}
}
......
......@@ -16,25 +16,26 @@
package com.google.android.exoplayer2.upstream;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
/**
* A {@link DataSource} for reading from a byte array.
*/
public final class ByteArrayDataSource implements DataSource {
/** A {@link DataSource} for reading from a byte array. */
public final class ByteArrayDataSource extends BaseDataSource {
private final byte[] data;
private Uri uri;
private @Nullable Uri uri;
private int readPosition;
private int bytesRemaining;
private boolean opened;
/**
* @param data The data to be read.
*/
public ByteArrayDataSource(byte[] data) {
super(DataSource.TYPE_LOCAL);
Assertions.checkNotNull(data);
Assertions.checkArgument(data.length > 0);
this.data = data;
......@@ -50,6 +51,8 @@ public final class ByteArrayDataSource implements DataSource {
throw new IOException("Unsatisfiable range: [" + readPosition + ", " + dataSpec.length
+ "], length: " + data.length);
}
opened = true;
transferStarted(dataSpec);
return bytesRemaining;
}
......@@ -65,16 +68,21 @@ public final class ByteArrayDataSource implements DataSource {
System.arraycopy(data, readPosition, buffer, offset, readLength);
readPosition += readLength;
bytesRemaining -= readLength;
bytesTransferred(readLength);
return readLength;
}
@Override
public Uri getUri() {
public @Nullable Uri getUri() {
return uri;
}
@Override
public void close() throws IOException {
if (opened) {
opened = false;
transferEnded();
}
uri = null;
}
......
......@@ -19,6 +19,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import java.io.EOFException;
import java.io.FileInputStream;
......@@ -26,10 +27,8 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.channels.FileChannel;
/**
* A {@link DataSource} for reading from a content URI.
*/
public final class ContentDataSource implements DataSource {
/** A {@link DataSource} for reading from a content URI. */
public final class ContentDataSource extends BaseDataSource {
/**
* Thrown when an {@link IOException} is encountered reading from a content URI.
......@@ -43,11 +42,10 @@ public final class ContentDataSource implements DataSource {
}
private final ContentResolver resolver;
private final TransferListener<? super ContentDataSource> listener;
private Uri uri;
private AssetFileDescriptor assetFileDescriptor;
private FileInputStream inputStream;
private @Nullable Uri uri;
private @Nullable AssetFileDescriptor assetFileDescriptor;
private @Nullable FileInputStream inputStream;
private long bytesRemaining;
private boolean opened;
......@@ -62,9 +60,13 @@ public final class ContentDataSource implements DataSource {
* @param context A context.
* @param listener An optional listener.
*/
public ContentDataSource(Context context, TransferListener<? super ContentDataSource> listener) {
public ContentDataSource(
Context context, @Nullable TransferListener<? super DataSource> listener) {
super(DataSource.TYPE_LOCAL);
this.resolver = context.getContentResolver();
this.listener = listener;
if (listener != null) {
addTransferListener(listener);
}
}
@Override
......@@ -102,9 +104,7 @@ public final class ContentDataSource implements DataSource {
}
opened = true;
if (listener != null) {
listener.onTransferStart(this, dataSpec);
}
transferStarted(dataSpec);
return bytesRemaining;
}
......@@ -136,14 +136,12 @@ public final class ContentDataSource implements DataSource {
if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead;
}
if (listener != null) {
listener.onBytesTransferred(this, bytesRead);
}
bytesTransferred(bytesRead);
return bytesRead;
}
@Override
public Uri getUri() {
public @Nullable Uri getUri() {
return uri;
}
......@@ -168,9 +166,7 @@ public final class ContentDataSource implements DataSource {
assetFileDescriptor = null;
if (opened) {
opened = false;
if (listener != null) {
listener.onTransferEnd(this);
}
transferEnded();
}
}
}
......
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.upstream;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.util.Base64;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
......@@ -23,16 +24,18 @@ import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.net.URLDecoder;
/**
* A {@link DataSource} for reading data URLs, as defined by RFC 2397.
*/
public final class DataSchemeDataSource implements DataSource {
/** A {@link DataSource} for reading data URLs, as defined by RFC 2397. */
public final class DataSchemeDataSource extends BaseDataSource {
public static final String SCHEME_DATA = "data";
private DataSpec dataSpec;
private @Nullable DataSpec dataSpec;
private int bytesRead;
private byte[] data;
private @Nullable byte[] data;
public DataSchemeDataSource() {
super(DataSource.TYPE_LOCAL);
}
@Override
public long open(DataSpec dataSpec) throws IOException {
......@@ -57,6 +60,7 @@ public final class DataSchemeDataSource implements DataSource {
// TODO: Add support for other charsets.
data = URLDecoder.decode(dataString, C.ASCII_NAME).getBytes();
}
transferStarted(dataSpec);
return data.length;
}
......@@ -72,18 +76,22 @@ public final class DataSchemeDataSource implements DataSource {
readLength = Math.min(readLength, remainingBytes);
System.arraycopy(data, bytesRead, buffer, offset, readLength);
bytesRead += readLength;
bytesTransferred(readLength);
return readLength;
}
@Override
public Uri getUri() {
public @Nullable Uri getUri() {
return dataSpec != null ? dataSpec.uri : null;
}
@Override
public void close() throws IOException {
if (data != null) {
data = null;
transferEnded();
}
dataSpec = null;
data = null;
}
}
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.upstream;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import com.google.android.exoplayer2.C;
......@@ -41,13 +42,13 @@ import java.util.regex.Pattern;
/**
* An {@link HttpDataSource} that uses Android's {@link HttpURLConnection}.
* <p>
* By default this implementation will not follow cross-protocol redirects (i.e. redirects from
* HTTP to HTTPS or vice versa). Cross-protocol redirects can be enabled by using the
* {@link #DefaultHttpDataSource(String, Predicate, TransferListener, int, int, boolean,
*
* <p>By default this implementation will not follow cross-protocol redirects (i.e. redirects from
* HTTP to HTTPS or vice versa). Cross-protocol redirects can be enabled by using the {@link
* #DefaultHttpDataSource(String, Predicate, TransferListener, int, int, boolean,
* RequestProperties)} constructor and passing {@code true} as the second last argument.
*/
public class DefaultHttpDataSource implements HttpDataSource {
public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSource {
/**
* The default connection timeout, in milliseconds.
......@@ -69,14 +70,13 @@ public class DefaultHttpDataSource implements HttpDataSource {
private final int connectTimeoutMillis;
private final int readTimeoutMillis;
private final String userAgent;
private final Predicate<String> contentTypePredicate;
private final RequestProperties defaultRequestProperties;
private final @Nullable Predicate<String> contentTypePredicate;
private final @Nullable RequestProperties defaultRequestProperties;
private final RequestProperties requestProperties;
private final TransferListener<? super DefaultHttpDataSource> listener;
private DataSpec dataSpec;
private HttpURLConnection connection;
private InputStream inputStream;
private @Nullable DataSpec dataSpec;
private @Nullable HttpURLConnection connection;
private @Nullable InputStream inputStream;
private boolean opened;
private long bytesToSkip;
......@@ -88,22 +88,24 @@ public class DefaultHttpDataSource implements HttpDataSource {
/**
* @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 HttpDataSource.InvalidContentTypeException} is thrown from
* {@link #open(DataSpec)}.
* predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link
* #open(DataSpec)}.
*/
public DefaultHttpDataSource(String userAgent, Predicate<String> contentTypePredicate) {
public DefaultHttpDataSource(String userAgent, @Nullable Predicate<String> contentTypePredicate) {
this(userAgent, contentTypePredicate, null);
}
/**
* @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 HttpDataSource.InvalidContentTypeException} is thrown from
* {@link #open(DataSpec)}.
* predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link
* #open(DataSpec)}.
* @param listener An optional listener.
*/
public DefaultHttpDataSource(String userAgent, Predicate<String> contentTypePredicate,
TransferListener<? super DefaultHttpDataSource> listener) {
public DefaultHttpDataSource(
String userAgent,
@Nullable Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super DataSource> listener) {
this(userAgent, contentTypePredicate, listener, DEFAULT_CONNECT_TIMEOUT_MILLIS,
DEFAULT_READ_TIMEOUT_MILLIS);
}
......@@ -111,16 +113,19 @@ public class DefaultHttpDataSource implements HttpDataSource {
/**
* @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 HttpDataSource.InvalidContentTypeException} is thrown from
* {@link #open(DataSpec)}.
* predicate then a {@link HttpDataSource.InvalidContentTypeException} is 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.
* @param readTimeoutMillis The read timeout, in milliseconds. A timeout of zero is interpreted as
* an infinite timeout.
*/
public DefaultHttpDataSource(String userAgent, Predicate<String> contentTypePredicate,
TransferListener<? super DefaultHttpDataSource> listener, int connectTimeoutMillis,
public DefaultHttpDataSource(
String userAgent,
@Nullable Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super DataSource> listener,
int connectTimeoutMillis,
int readTimeoutMillis) {
this(userAgent, contentTypePredicate, listener, connectTimeoutMillis, readTimeoutMillis, false,
null);
......@@ -129,35 +134,42 @@ public class DefaultHttpDataSource implements HttpDataSource {
/**
* @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 HttpDataSource.InvalidContentTypeException} is thrown from
* {@link #open(DataSpec)}.
* predicate then a {@link HttpDataSource.InvalidContentTypeException} is 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. Pass {@link #DEFAULT_CONNECT_TIMEOUT_MILLIS} to use
* the default value.
* @param readTimeoutMillis The read timeout, in milliseconds. A timeout of zero is interpreted
* as an infinite timeout. Pass {@link #DEFAULT_READ_TIMEOUT_MILLIS} to use the default value.
* interpreted as an infinite timeout. Pass {@link #DEFAULT_CONNECT_TIMEOUT_MILLIS} to use the
* default value.
* @param readTimeoutMillis The read timeout, in milliseconds. A timeout of zero is interpreted as
* an infinite timeout. Pass {@link #DEFAULT_READ_TIMEOUT_MILLIS} to use the default value.
* @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP
* to HTTPS and vice versa) are enabled.
* @param defaultRequestProperties The default request properties to be sent to the server as
* HTTP headers or {@code null} if not required.
* @param defaultRequestProperties The default request properties to be sent to the server as HTTP
* headers or {@code null} if not required.
*/
public DefaultHttpDataSource(String userAgent, Predicate<String> contentTypePredicate,
TransferListener<? super DefaultHttpDataSource> listener, int connectTimeoutMillis,
int readTimeoutMillis, boolean allowCrossProtocolRedirects,
RequestProperties defaultRequestProperties) {
public DefaultHttpDataSource(
String userAgent,
@Nullable Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super DataSource> listener,
int connectTimeoutMillis,
int readTimeoutMillis,
boolean allowCrossProtocolRedirects,
@Nullable RequestProperties defaultRequestProperties) {
super(DataSource.TYPE_REMOTE);
this.userAgent = Assertions.checkNotEmpty(userAgent);
this.contentTypePredicate = contentTypePredicate;
this.listener = listener;
this.requestProperties = new RequestProperties();
this.connectTimeoutMillis = connectTimeoutMillis;
this.readTimeoutMillis = readTimeoutMillis;
this.allowCrossProtocolRedirects = allowCrossProtocolRedirects;
this.defaultRequestProperties = defaultRequestProperties;
if (listener != null) {
addTransferListener(listener);
}
}
@Override
public Uri getUri() {
public @Nullable Uri getUri() {
return connection == null ? null : Uri.parse(connection.getURL().toString());
}
......@@ -254,9 +266,7 @@ public class DefaultHttpDataSource implements HttpDataSource {
}
opened = true;
if (listener != null) {
listener.onTransferStart(this, dataSpec);
}
transferStarted(dataSpec);
return bytesToRead;
}
......@@ -287,9 +297,7 @@ public class DefaultHttpDataSource implements HttpDataSource {
closeConnectionQuietly();
if (opened) {
opened = false;
if (listener != null) {
listener.onTransferEnd(this);
}
transferEnded();
}
}
}
......@@ -534,9 +542,7 @@ public class DefaultHttpDataSource implements HttpDataSource {
throw new EOFException();
}
bytesSkipped += read;
if (listener != null) {
listener.onBytesTransferred(this, read);
}
bytesTransferred(read);
}
// Release the shared skip buffer.
......@@ -579,9 +585,7 @@ public class DefaultHttpDataSource implements HttpDataSource {
}
bytesRead += read;
if (listener != null) {
listener.onBytesTransferred(this, read);
}
bytesTransferred(read);
return read;
}
......
......@@ -16,15 +16,14 @@
package com.google.android.exoplayer2.upstream;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import java.io.EOFException;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* A {@link DataSource} for reading local files.
*/
public final class FileDataSource implements DataSource {
/** A {@link DataSource} for reading local files. */
public final class FileDataSource extends BaseDataSource {
/**
* Thrown when IOException is encountered during local file read operation.
......@@ -37,10 +36,8 @@ public final class FileDataSource implements DataSource {
}
private final TransferListener<? super FileDataSource> listener;
private RandomAccessFile file;
private Uri uri;
private @Nullable RandomAccessFile file;
private @Nullable Uri uri;
private long bytesRemaining;
private boolean opened;
......@@ -48,11 +45,12 @@ public final class FileDataSource implements DataSource {
this(null);
}
/**
* @param listener An optional listener.
*/
public FileDataSource(TransferListener<? super FileDataSource> listener) {
this.listener = listener;
/** @param listener An optional listener. */
public FileDataSource(@Nullable TransferListener<? super DataSource> listener) {
super(DataSource.TYPE_LOCAL);
if (listener != null) {
addTransferListener(listener);
}
}
@Override
......@@ -71,9 +69,7 @@ public final class FileDataSource implements DataSource {
}
opened = true;
if (listener != null) {
listener.onTransferStart(this, dataSpec);
}
transferStarted(dataSpec);
return bytesRemaining;
}
......@@ -94,9 +90,7 @@ public final class FileDataSource implements DataSource {
if (bytesRead > 0) {
bytesRemaining -= bytesRead;
if (listener != null) {
listener.onBytesTransferred(this, bytesRead);
}
bytesTransferred(bytesRead);
}
return bytesRead;
......@@ -104,7 +98,7 @@ public final class FileDataSource implements DataSource {
}
@Override
public Uri getUri() {
public @Nullable Uri getUri() {
return uri;
}
......@@ -121,9 +115,7 @@ public final class FileDataSource implements DataSource {
file = null;
if (opened) {
opened = false;
if (listener != null) {
listener.onTransferEnd(this);
}
transferEnded();
}
}
}
......
......@@ -15,18 +15,20 @@
*/
package com.google.android.exoplayer2.upstream;
import android.support.annotation.Nullable;
/**
* A {@link DataSource.Factory} that produces {@link FileDataSource}.
*/
public final class FileDataSourceFactory implements DataSource.Factory {
private final TransferListener<? super FileDataSource> listener;
private final @Nullable TransferListener<? super DataSource> listener;
public FileDataSourceFactory() {
this(null);
}
public FileDataSourceFactory(TransferListener<? super FileDataSource> listener) {
public FileDataSourceFactory(@Nullable TransferListener<? super DataSource> listener) {
this.listener = listener;
}
......
......@@ -19,6 +19,7 @@ import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.google.android.exoplayer2.C;
import java.io.EOFException;
......@@ -28,12 +29,12 @@ import java.io.InputStream;
/**
* A {@link DataSource} for reading a raw resource inside the APK.
* <p>
* URIs supported by this source are of the form {@code rawresource:///rawResourceId}, where
*
* <p>URIs supported by this source are of the form {@code rawresource:///rawResourceId}, where
* rawResourceId is the integer identifier of a raw resource. {@link #buildRawResourceUri(int)} can
* be used to build {@link Uri}s in this format.
*/
public final class RawResourceDataSource implements DataSource {
public final class RawResourceDataSource extends BaseDataSource {
/**
* Thrown when an {@link IOException} is encountered reading from a raw resource.
......@@ -62,11 +63,10 @@ public final class RawResourceDataSource implements DataSource {
public static final String RAW_RESOURCE_SCHEME = "rawresource";
private final Resources resources;
private final TransferListener<? super RawResourceDataSource> listener;
private Uri uri;
private AssetFileDescriptor assetFileDescriptor;
private InputStream inputStream;
private @Nullable Uri uri;
private @Nullable AssetFileDescriptor assetFileDescriptor;
private @Nullable InputStream inputStream;
private long bytesRemaining;
private boolean opened;
......@@ -81,10 +81,13 @@ public final class RawResourceDataSource implements DataSource {
* @param context A context.
* @param listener An optional listener.
*/
public RawResourceDataSource(Context context,
TransferListener<? super RawResourceDataSource> listener) {
public RawResourceDataSource(
Context context, @Nullable TransferListener<? super DataSource> listener) {
super(DataSource.TYPE_LOCAL);
this.resources = context.getResources();
this.listener = listener;
if (listener != null) {
addTransferListener(listener);
}
}
@Override
......@@ -124,9 +127,7 @@ public final class RawResourceDataSource implements DataSource {
}
opened = true;
if (listener != null) {
listener.onTransferStart(this, dataSpec);
}
transferStarted(dataSpec);
return bytesRemaining;
}
......@@ -158,14 +159,12 @@ public final class RawResourceDataSource implements DataSource {
if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead;
}
if (listener != null) {
listener.onBytesTransferred(this, bytesRead);
}
bytesTransferred(bytesRead);
return bytesRead;
}
@Override
public Uri getUri() {
public @Nullable Uri getUri() {
return uri;
}
......@@ -190,9 +189,7 @@ public final class RawResourceDataSource implements DataSource {
assetFileDescriptor = null;
if (opened) {
opened = false;
if (listener != null) {
listener.onTransferEnd(this);
}
transferEnded();
}
}
}
......
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.upstream;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import java.io.IOException;
import java.net.DatagramPacket;
......@@ -25,10 +26,8 @@ import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.SocketException;
/**
* A UDP {@link DataSource}.
*/
public final class UdpDataSource implements DataSource {
/** A UDP {@link DataSource}. */
public final class UdpDataSource extends BaseDataSource {
/**
* Thrown when an error is encountered when trying to read from a {@link UdpDataSource}.
......@@ -51,24 +50,21 @@ public final class UdpDataSource implements DataSource {
*/
public static final int DEAFULT_SOCKET_TIMEOUT_MILLIS = 8 * 1000;
private final TransferListener<? super UdpDataSource> listener;
private final int socketTimeoutMillis;
private final byte[] packetBuffer;
private final DatagramPacket packet;
private Uri uri;
private DatagramSocket socket;
private MulticastSocket multicastSocket;
private InetAddress address;
private InetSocketAddress socketAddress;
private @Nullable Uri uri;
private @Nullable DatagramSocket socket;
private @Nullable MulticastSocket multicastSocket;
private @Nullable InetAddress address;
private @Nullable InetSocketAddress socketAddress;
private boolean opened;
private int packetRemaining;
/**
* @param listener An optional listener.
*/
public UdpDataSource(TransferListener<? super UdpDataSource> listener) {
/** @param listener An optional listener. */
public UdpDataSource(@Nullable TransferListener<? super DataSource> listener) {
this(listener, DEFAULT_MAX_PACKET_SIZE);
}
......@@ -76,7 +72,7 @@ public final class UdpDataSource implements DataSource {
* @param listener An optional listener.
* @param maxPacketSize The maximum datagram packet size, in bytes.
*/
public UdpDataSource(TransferListener<? super UdpDataSource> listener, int maxPacketSize) {
public UdpDataSource(@Nullable TransferListener<? super DataSource> listener, int maxPacketSize) {
this(listener, maxPacketSize, DEAFULT_SOCKET_TIMEOUT_MILLIS);
}
......@@ -86,12 +82,17 @@ public final class UdpDataSource implements DataSource {
* @param socketTimeoutMillis The socket timeout in milliseconds. A timeout of zero is interpreted
* as an infinite timeout.
*/
public UdpDataSource(TransferListener<? super UdpDataSource> listener, int maxPacketSize,
public UdpDataSource(
@Nullable TransferListener<? super DataSource> listener,
int maxPacketSize,
int socketTimeoutMillis) {
this.listener = listener;
super(DataSource.TYPE_REMOTE);
this.socketTimeoutMillis = socketTimeoutMillis;
packetBuffer = new byte[maxPacketSize];
packet = new DatagramPacket(packetBuffer, 0, maxPacketSize);
if (listener != null) {
addTransferListener(listener);
}
}
@Override
......@@ -121,9 +122,7 @@ public final class UdpDataSource implements DataSource {
}
opened = true;
if (listener != null) {
listener.onTransferStart(this, dataSpec);
}
transferStarted(dataSpec);
return C.LENGTH_UNSET;
}
......@@ -141,9 +140,7 @@ public final class UdpDataSource implements DataSource {
throw new UdpDataSourceException(e);
}
packetRemaining = packet.getLength();
if (listener != null) {
listener.onBytesTransferred(this, packetRemaining);
}
bytesTransferred(packetRemaining);
}
int packetOffset = packet.getLength() - packetRemaining;
......@@ -154,7 +151,7 @@ public final class UdpDataSource implements DataSource {
}
@Override
public Uri getUri() {
public @Nullable Uri getUri() {
return uri;
}
......@@ -178,9 +175,7 @@ public final class UdpDataSource implements DataSource {
packetRemaining = 0;
if (opened) {
opened = false;
if (listener != null) {
listener.onTransferEnd(this);
}
transferEnded();
}
}
......
......@@ -20,6 +20,7 @@ import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData;
import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData.Segment;
import com.google.android.exoplayer2.upstream.BaseDataSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSourceException;
import com.google.android.exoplayer2.upstream.DataSpec;
......@@ -32,18 +33,20 @@ import java.util.ArrayList;
* A fake {@link DataSource} capable of simulating various scenarios. It uses a {@link FakeDataSet}
* instance which determines the response to data access calls.
*/
public class FakeDataSource implements DataSource {
public class FakeDataSource extends BaseDataSource {
/**
* Factory to create a {@link FakeDataSource}.
*/
public static class Factory implements DataSource.Factory {
protected final TransferListener<? super FakeDataSource> transferListener;
protected final TransferListener<? super DataSource> transferListener;
protected FakeDataSet fakeDataSet;
protected @DataSource.Type int dataSourceType;
public Factory(@Nullable TransferListener<? super FakeDataSource> transferListener) {
public Factory(@Nullable TransferListener<? super DataSource> transferListener) {
this.transferListener = transferListener;
this.dataSourceType = DataSource.TYPE_LOCAL;
}
public final Factory setFakeDataSet(FakeDataSet fakeDataSet) {
......@@ -51,19 +54,23 @@ public class FakeDataSource implements DataSource {
return this;
}
public final Factory setDataSourceType(@DataSource.Type int dataSourceType) {
this.dataSourceType = dataSourceType;
return this;
}
@Override
public DataSource createDataSource() {
return new FakeDataSource(fakeDataSet, transferListener);
return new FakeDataSource(fakeDataSet, transferListener, dataSourceType);
}
}
private final FakeDataSet fakeDataSet;
private final TransferListener<? super FakeDataSource> transferListener;
private final ArrayList<DataSpec> openedDataSpecs;
private Uri uri;
private boolean opened;
private boolean openCalled;
private boolean sourceOpened;
private FakeData fakeData;
private int currentSegmentIndex;
private long bytesRemaining;
......@@ -73,15 +80,20 @@ public class FakeDataSource implements DataSource {
}
public FakeDataSource(FakeDataSet fakeDataSet) {
this(fakeDataSet, null);
this(fakeDataSet, null, DataSource.TYPE_LOCAL);
}
public FakeDataSource(FakeDataSet fakeDataSet,
@Nullable TransferListener<? super FakeDataSource> transferListener) {
public FakeDataSource(
FakeDataSet fakeDataSet,
@Nullable TransferListener<? super DataSource> transferListener,
@DataSource.Type int dataSourceType) {
super(dataSourceType);
Assertions.checkNotNull(fakeDataSet);
this.fakeDataSet = fakeDataSet;
this.transferListener = transferListener;
this.openedDataSpecs = new ArrayList<>();
if (transferListener != null) {
addTransferListener(transferListener);
}
}
public final FakeDataSet getDataSet() {
......@@ -90,9 +102,9 @@ public class FakeDataSource implements DataSource {
@Override
public final long open(DataSpec dataSpec) throws IOException {
Assertions.checkState(!opened);
Assertions.checkState(!openCalled);
openCalled = true;
// DataSpec requires a matching close call even if open fails.
opened = true;
uri = dataSpec.uri;
openedDataSpecs.add(dataSpec);
......@@ -129,9 +141,8 @@ public class FakeDataSource implements DataSource {
currentSegmentIndex++;
}
}
if (transferListener != null) {
transferListener.onTransferStart(this, dataSpec);
}
sourceOpened = true;
transferStarted(dataSpec);
// Configure bytesRemaining, and return.
if (dataSpec.length == C.LENGTH_UNSET) {
bytesRemaining = totalLength - dataSpec.position;
......@@ -144,7 +155,7 @@ public class FakeDataSource implements DataSource {
@Override
public final int read(byte[] buffer, int offset, int readLength) throws IOException {
Assertions.checkState(opened);
Assertions.checkState(sourceOpened);
while (true) {
if (currentSegmentIndex == fakeData.getSegments().size() || bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT;
......@@ -171,9 +182,7 @@ public class FakeDataSource implements DataSource {
System.arraycopy(current.data, current.bytesRead, buffer, offset, readLength);
}
onDataRead(readLength);
if (transferListener != null) {
transferListener.onBytesTransferred(this, readLength);
}
bytesTransferred(readLength);
bytesRemaining -= readLength;
current.bytesRead += readLength;
if (current.bytesRead == current.length) {
......@@ -191,8 +200,8 @@ public class FakeDataSource implements DataSource {
@Override
public final void close() throws IOException {
Assertions.checkState(opened);
opened = false;
Assertions.checkState(openCalled);
openCalled = false;
uri = null;
if (fakeData != null && currentSegmentIndex < fakeData.getSegments().size()) {
Segment current = fakeData.getSegments().get(currentSegmentIndex);
......@@ -200,8 +209,9 @@ public class FakeDataSource implements DataSource {
current.exceptionCleared = true;
}
}
if (transferListener != null) {
transferListener.onTransferEnd(this);
if (sourceOpened) {
sourceOpened = false;
transferEnded();
}
fakeData = null;
}
......@@ -219,7 +229,7 @@ public class FakeDataSource implements DataSource {
/** Returns whether the data source is currently opened. */
public final boolean isOpened() {
return opened;
return sourceOpened;
}
protected void onDataRead(int bytesRead) throws IOException {
......
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