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
This commit is contained in:
tonihei 2018-07-03 03:44:00 -07:00 committed by Oliver Woodman
parent e48b712a32
commit 985160a47d
15 changed files with 486 additions and 459 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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;
}
@Override
public DataSource createDataSource() {
return new FakeDataSource(fakeDataSet, transferListener);
public final Factory setDataSourceType(@DataSource.Type int dataSourceType) {
this.dataSourceType = dataSourceType;
return this;
}
@Override
public DataSource createDataSource() {
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 {