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; package com.google.android.exoplayer2.ext.cronet;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; 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.DataSourceException;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource; 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. * DataSource without intermediate buffer based on Cronet API set using UrlRequest.
*
* <p>This class's methods are organized in the sequence of expected calls. * <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}. * 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; 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 TAG = "CronetDataSource";
private static final String CONTENT_TYPE = "Content-Type"; private static final String CONTENT_TYPE = "Content-Type";
private static final String SET_COOKIE = "Set-Cookie"; 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 CronetEngine cronetEngine;
private final Executor executor; private final Executor executor;
private final Predicate<String> contentTypePredicate; private final Predicate<String> contentTypePredicate;
private final TransferListener<? super CronetDataSource> listener;
private final int connectTimeoutMs; private final int connectTimeoutMs;
private final int readTimeoutMs; private final int readTimeoutMs;
private final boolean resetTimeoutOnRedirects; private final boolean resetTimeoutOnRedirects;
@ -144,41 +149,49 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
/** /**
* @param cronetEngine A CronetEngine. * @param cronetEngine A CronetEngine.
* @param executor The {@link java.util.concurrent.Executor} that will handle responses. * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may
* This may be a direct executor (i.e. executes tasks on the calling thread) in order * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread
* to avoid a thread hop from Cronet's internal network thread to the response handling * hop from Cronet's internal network thread to the response handling thread. However, to
* thread. However, to avoid slowing down overall network performance, care must be taken * avoid slowing down overall network performance, care must be taken to make sure response
* to make sure response handling is a fast operation when using a direct executor. * handling is a fast operation when using a direct executor.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then an {@link InvalidContentTypeException} is thrown from * predicate then an {@link InvalidContentTypeException} is thrown from {@link
* {@link #open(DataSpec)}. * #open(DataSpec)}.
* @param listener An optional listener. * @param listener An optional listener.
*/ */
public CronetDataSource(CronetEngine cronetEngine, Executor executor, public CronetDataSource(
Predicate<String> contentTypePredicate, TransferListener<? super CronetDataSource> listener) { CronetEngine cronetEngine,
Executor executor,
Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super DataSource> listener) {
this(cronetEngine, executor, contentTypePredicate, listener, DEFAULT_CONNECT_TIMEOUT_MILLIS, this(cronetEngine, executor, contentTypePredicate, listener, DEFAULT_CONNECT_TIMEOUT_MILLIS,
DEFAULT_READ_TIMEOUT_MILLIS, false, null, false); DEFAULT_READ_TIMEOUT_MILLIS, false, null, false);
} }
/** /**
* @param cronetEngine A CronetEngine. * @param cronetEngine A CronetEngine.
* @param executor The {@link java.util.concurrent.Executor} that will handle responses. * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may
* This may be a direct executor (i.e. executes tasks on the calling thread) in order * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread
* to avoid a thread hop from Cronet's internal network thread to the response handling * hop from Cronet's internal network thread to the response handling thread. However, to
* thread. However, to avoid slowing down overall network performance, care must be taken * avoid slowing down overall network performance, care must be taken to make sure response
* to make sure response handling is a fast operation when using a direct executor. * handling is a fast operation when using a direct executor.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then an {@link InvalidContentTypeException} is thrown from * predicate then an {@link InvalidContentTypeException} is thrown from {@link
* {@link #open(DataSpec)}. * #open(DataSpec)}.
* @param listener An optional listener. * @param listener An optional listener.
* @param connectTimeoutMs The connection timeout, in milliseconds. * @param connectTimeoutMs The connection timeout, in milliseconds.
* @param readTimeoutMs The read timeout, in milliseconds. * @param readTimeoutMs The read timeout, in milliseconds.
* @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs. * @param resetTimeoutOnRedirects Whether the connect timeout is reset when a redirect occurs.
* @param defaultRequestProperties The default request properties to be used. * @param defaultRequestProperties The default request properties to be used.
*/ */
public CronetDataSource(CronetEngine cronetEngine, Executor executor, public CronetDataSource(
Predicate<String> contentTypePredicate, TransferListener<? super CronetDataSource> listener, CronetEngine cronetEngine,
int connectTimeoutMs, int readTimeoutMs, boolean resetTimeoutOnRedirects, Executor executor,
Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super DataSource> listener,
int connectTimeoutMs,
int readTimeoutMs,
boolean resetTimeoutOnRedirects,
RequestProperties defaultRequestProperties) { RequestProperties defaultRequestProperties) {
this(cronetEngine, executor, contentTypePredicate, listener, connectTimeoutMs, this(cronetEngine, executor, contentTypePredicate, listener, connectTimeoutMs,
readTimeoutMs, resetTimeoutOnRedirects, Clock.DEFAULT, defaultRequestProperties, false); readTimeoutMs, resetTimeoutOnRedirects, Clock.DEFAULT, defaultRequestProperties, false);
@ -186,14 +199,14 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
/** /**
* @param cronetEngine A CronetEngine. * @param cronetEngine A CronetEngine.
* @param executor The {@link java.util.concurrent.Executor} that will handle responses. * @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may
* This may be a direct executor (i.e. executes tasks on the calling thread) in order * be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread
* to avoid a thread hop from Cronet's internal network thread to the response handling * hop from Cronet's internal network thread to the response handling thread. However, to
* thread. However, to avoid slowing down overall network performance, care must be taken * avoid slowing down overall network performance, care must be taken to make sure response
* to make sure response handling is a fast operation when using a direct executor. * handling is a fast operation when using a direct executor.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then an {@link InvalidContentTypeException} is thrown from * predicate then an {@link InvalidContentTypeException} is thrown from {@link
* {@link #open(DataSpec)}. * #open(DataSpec)}.
* @param listener An optional listener. * @param listener An optional listener.
* @param connectTimeoutMs The connection timeout, in milliseconds. * @param connectTimeoutMs The connection timeout, in milliseconds.
* @param readTimeoutMs The read 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 * @param handleSetCookieRequests Whether "Set-Cookie" requests on redirect should be forwarded to
* the redirect url in the "Cookie" header. * the redirect url in the "Cookie" header.
*/ */
public CronetDataSource(CronetEngine cronetEngine, Executor executor, public CronetDataSource(
Predicate<String> contentTypePredicate, TransferListener<? super CronetDataSource> listener, CronetEngine cronetEngine,
int connectTimeoutMs, int readTimeoutMs, boolean resetTimeoutOnRedirects, Executor executor,
RequestProperties defaultRequestProperties, boolean handleSetCookieRequests) { Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super DataSource> listener,
int connectTimeoutMs,
int readTimeoutMs,
boolean resetTimeoutOnRedirects,
RequestProperties defaultRequestProperties,
boolean handleSetCookieRequests) {
this(cronetEngine, executor, contentTypePredicate, listener, connectTimeoutMs, this(cronetEngine, executor, contentTypePredicate, listener, connectTimeoutMs,
readTimeoutMs, resetTimeoutOnRedirects, Clock.DEFAULT, defaultRequestProperties, readTimeoutMs, resetTimeoutOnRedirects, Clock.DEFAULT, defaultRequestProperties,
handleSetCookieRequests); handleSetCookieRequests);
} }
/* package */ CronetDataSource(CronetEngine cronetEngine, Executor executor, /* package */ CronetDataSource(
Predicate<String> contentTypePredicate, TransferListener<? super CronetDataSource> listener, CronetEngine cronetEngine,
int connectTimeoutMs, int readTimeoutMs, boolean resetTimeoutOnRedirects, Clock clock, Executor executor,
RequestProperties defaultRequestProperties, boolean handleSetCookieRequests) { 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.cronetEngine = Assertions.checkNotNull(cronetEngine);
this.executor = Assertions.checkNotNull(executor); this.executor = Assertions.checkNotNull(executor);
this.contentTypePredicate = contentTypePredicate; this.contentTypePredicate = contentTypePredicate;
this.listener = listener;
this.connectTimeoutMs = connectTimeoutMs; this.connectTimeoutMs = connectTimeoutMs;
this.readTimeoutMs = readTimeoutMs; this.readTimeoutMs = readTimeoutMs;
this.resetTimeoutOnRedirects = resetTimeoutOnRedirects; this.resetTimeoutOnRedirects = resetTimeoutOnRedirects;
@ -227,6 +254,9 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
this.handleSetCookieRequests = handleSetCookieRequests; this.handleSetCookieRequests = handleSetCookieRequests;
requestProperties = new RequestProperties(); requestProperties = new RequestProperties();
operation = new ConditionVariable(); operation = new ConditionVariable();
if (listener != null) {
addTransferListener(listener);
}
} }
// HttpDataSource implementation. // HttpDataSource implementation.
@ -324,9 +354,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
} }
opened = true; opened = true;
if (listener != null) { transferStarted(dataSpec);
listener.onTransferStart(this, dataSpec);
}
return bytesRemaining; return bytesRemaining;
} }
@ -392,9 +420,7 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
if (bytesRemaining != C.LENGTH_UNSET) { if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead; bytesRemaining -= bytesRead;
} }
if (listener != null) { bytesTransferred(bytesRead);
listener.onBytesTransferred(this, bytesRead);
}
return bytesRead; return bytesRead;
} }
@ -413,107 +439,17 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
finished = false; finished = false;
if (opened) { if (opened) {
opened = false; opened = false;
if (listener != null) { transferEnded();
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;
}
if (error instanceof NetworkException
&& ((NetworkException) error).getErrorCode()
== NetworkException.ERROR_HOSTNAME_NOT_RESOLVED) {
exception = new UnknownHostException();
} else {
exception = error;
}
operation.open();
}
// Internal methods. // Internal methods.
private UrlRequest.Builder buildRequestBuilder(DataSpec dataSpec) throws IOException { private UrlRequest.Builder buildRequestBuilder(DataSpec dataSpec) throws IOException {
UrlRequest.Builder requestBuilder = cronetEngine.newUrlRequestBuilder( UrlRequest.Builder requestBuilder =
dataSpec.uri.toString(), this, executor).allowDirectExecutor(); cronetEngine
.newUrlRequestBuilder(dataSpec.uri.toString(), urlRequestCallback, executor)
.allowDirectExecutor();
// Set the headers. // Set the headers.
boolean isContentTypeHeaderSet = false; boolean isContentTypeHeaderSet = false;
if (defaultRequestProperties != null) { if (defaultRequestProperties != null) {
@ -656,4 +592,99 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
return list == null || list.isEmpty(); 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.doThrow;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -33,6 +32,7 @@ import android.net.Uri;
import android.os.ConditionVariable; import android.os.ConditionVariable;
import android.os.SystemClock; import android.os.SystemClock;
import com.google.android.exoplayer2.C; 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.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource.HttpDataSourceException; import com.google.android.exoplayer2.upstream.HttpDataSource.HttpDataSourceException;
@ -87,7 +87,7 @@ public final class CronetDataSourceTest {
@Mock private UrlRequest.Builder mockUrlRequestBuilder; @Mock private UrlRequest.Builder mockUrlRequestBuilder;
@Mock private UrlRequest mockUrlRequest; @Mock private UrlRequest mockUrlRequest;
@Mock private Predicate<String> mockContentTypePredicate; @Mock private Predicate<String> mockContentTypePredicate;
@Mock private TransferListener<CronetDataSource> mockTransferListener; @Mock private TransferListener<DataSource> mockTransferListener;
@Mock private Executor mockExecutor; @Mock private Executor mockExecutor;
@Mock private NetworkException mockNetworkException; @Mock private NetworkException mockNetworkException;
@Mock private CronetEngine mockCronetEngine; @Mock private CronetEngine mockCronetEngine;
@ -99,18 +99,17 @@ public final class CronetDataSourceTest {
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
dataSourceUnderTest = dataSourceUnderTest =
spy( new CronetDataSource(
new CronetDataSource( mockCronetEngine,
mockCronetEngine, mockExecutor,
mockExecutor, mockContentTypePredicate,
mockContentTypePredicate, mockTransferListener,
mockTransferListener, TEST_CONNECT_TIMEOUT_MS,
TEST_CONNECT_TIMEOUT_MS, TEST_READ_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS, true, // resetTimeoutOnRedirects
true, // resetTimeoutOnRedirects Clock.DEFAULT,
Clock.DEFAULT, null,
null, false);
false));
when(mockContentTypePredicate.evaluate(anyString())).thenReturn(true); when(mockContentTypePredicate.evaluate(anyString())).thenReturn(true);
when(mockCronetEngine.newUrlRequestBuilder( when(mockCronetEngine.newUrlRequestBuilder(
anyString(), any(UrlRequest.Callback.class), any(Executor.class))) anyString(), any(UrlRequest.Callback.class), any(Executor.class)))
@ -172,9 +171,10 @@ public final class CronetDataSourceTest {
@Override @Override
public Object answer(InvocationOnMock invocation) throws Throwable { public Object answer(InvocationOnMock invocation) throws Throwable {
// Invoke the callback for the previous request. // Invoke the callback for the previous request.
dataSourceUnderTest.onFailed( dataSourceUnderTest.urlRequestCallback.onFailed(
mockUrlRequest, testUrlResponseInfo, mockNetworkException); mockUrlRequest, testUrlResponseInfo, mockNetworkException);
dataSourceUnderTest.onResponseStarted(mockUrlRequest2, testUrlResponseInfo); dataSourceUnderTest.urlRequestCallback.onResponseStarted(
mockUrlRequest2, testUrlResponseInfo);
return null; return null;
} }
}) })
@ -601,7 +601,7 @@ public final class CronetDataSourceTest {
} }
@Test @Test
public void testConnectResponseBeforeTimeout() throws InterruptedException { public void testConnectResponseBeforeTimeout() throws Exception {
long startTimeMs = SystemClock.elapsedRealtime(); long startTimeMs = SystemClock.elapsedRealtime();
final ConditionVariable startCondition = buildUrlRequestStartedCondition(); final ConditionVariable startCondition = buildUrlRequestStartedCondition();
final CountDownLatch openLatch = new CountDownLatch(1); final CountDownLatch openLatch = new CountDownLatch(1);
@ -625,12 +625,12 @@ public final class CronetDataSourceTest {
ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
assertNotCountedDown(openLatch); assertNotCountedDown(openLatch);
// The response arrives just in time. // The response arrives just in time.
dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo); dataSourceUnderTest.urlRequestCallback.onResponseStarted(mockUrlRequest, testUrlResponseInfo);
openLatch.await(); openLatch.await();
} }
@Test @Test
public void testRedirectIncreasesConnectionTimeout() throws InterruptedException { public void testRedirectIncreasesConnectionTimeout() throws Exception {
long startTimeMs = SystemClock.elapsedRealtime(); long startTimeMs = SystemClock.elapsedRealtime();
final ConditionVariable startCondition = buildUrlRequestStartedCondition(); final ConditionVariable startCondition = buildUrlRequestStartedCondition();
final CountDownLatch timedOutLatch = new CountDownLatch(1); final CountDownLatch timedOutLatch = new CountDownLatch(1);
@ -659,7 +659,7 @@ public final class CronetDataSourceTest {
ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
assertNotCountedDown(timedOutLatch); assertNotCountedDown(timedOutLatch);
// A redirect arrives just in time. // A redirect arrives just in time.
dataSourceUnderTest.onRedirectReceived( dataSourceUnderTest.urlRequestCallback.onRedirectReceived(
mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl1"); mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl1");
long newTimeoutMs = 2 * TEST_CONNECT_TIMEOUT_MS - 1; 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. // We should still be trying to open as we approach the new timeout.
assertNotCountedDown(timedOutLatch); assertNotCountedDown(timedOutLatch);
// A redirect arrives just in time. // A redirect arrives just in time.
dataSourceUnderTest.onRedirectReceived( dataSourceUnderTest.urlRequestCallback.onRedirectReceived(
mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl2"); mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl2");
newTimeoutMs = 3 * TEST_CONNECT_TIMEOUT_MS - 2; newTimeoutMs = 3 * TEST_CONNECT_TIMEOUT_MS - 2;
@ -700,18 +700,17 @@ public final class CronetDataSourceTest {
testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeaders() testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeaders()
throws HttpDataSourceException { throws HttpDataSourceException {
dataSourceUnderTest = dataSourceUnderTest =
spy( new CronetDataSource(
new CronetDataSource( mockCronetEngine,
mockCronetEngine, mockExecutor,
mockExecutor, mockContentTypePredicate,
mockContentTypePredicate, mockTransferListener,
mockTransferListener, TEST_CONNECT_TIMEOUT_MS,
TEST_CONNECT_TIMEOUT_MS, TEST_READ_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS, true, // resetTimeoutOnRedirects
true, // resetTimeoutOnRedirects Clock.DEFAULT,
Clock.DEFAULT, null,
null, true);
true));
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE); dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
mockSingleRedirectSuccess(); mockSingleRedirectSuccess();
@ -732,18 +731,17 @@ public final class CronetDataSourceTest {
throws HttpDataSourceException { throws HttpDataSourceException {
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null); testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
dataSourceUnderTest = dataSourceUnderTest =
spy( new CronetDataSource(
new CronetDataSource( mockCronetEngine,
mockCronetEngine, mockExecutor,
mockExecutor, mockContentTypePredicate,
mockContentTypePredicate, mockTransferListener,
mockTransferListener, TEST_CONNECT_TIMEOUT_MS,
TEST_CONNECT_TIMEOUT_MS, TEST_READ_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS, true, // resetTimeoutOnRedirects
true, // resetTimeoutOnRedirects Clock.DEFAULT,
Clock.DEFAULT, null,
null, true);
true));
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE); dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
mockSingleRedirectSuccess(); mockSingleRedirectSuccess();
@ -772,18 +770,17 @@ public final class CronetDataSourceTest {
public void testRedirectNoSetCookieFollowsRedirect_dataSourceHandlesSetCookie() public void testRedirectNoSetCookieFollowsRedirect_dataSourceHandlesSetCookie()
throws HttpDataSourceException { throws HttpDataSourceException {
dataSourceUnderTest = dataSourceUnderTest =
spy( new CronetDataSource(
new CronetDataSource( mockCronetEngine,
mockCronetEngine, mockExecutor,
mockExecutor, mockContentTypePredicate,
mockContentTypePredicate, mockTransferListener,
mockTransferListener, TEST_CONNECT_TIMEOUT_MS,
TEST_CONNECT_TIMEOUT_MS, TEST_READ_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS, true, // resetTimeoutOnRedirects
true, // resetTimeoutOnRedirects Clock.DEFAULT,
Clock.DEFAULT, null,
null, true);
true));
mockSingleRedirectSuccess(); mockSingleRedirectSuccess();
mockFollowRedirectSuccess(); mockFollowRedirectSuccess();
@ -889,7 +886,8 @@ public final class CronetDataSourceTest {
new Answer<Object>() { new Answer<Object>() {
@Override @Override
public Object answer(InvocationOnMock invocation) throws Throwable { public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo); dataSourceUnderTest.urlRequestCallback.onResponseStarted(
mockUrlRequest, testUrlResponseInfo);
return null; return null;
} }
}) })
@ -902,7 +900,7 @@ public final class CronetDataSourceTest {
new Answer<Object>() { new Answer<Object>() {
@Override @Override
public Object answer(InvocationOnMock invocation) throws Throwable { public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onRedirectReceived( dataSourceUnderTest.urlRequestCallback.onRedirectReceived(
mockUrlRequest, mockUrlRequest,
createUrlResponseInfo(307), // statusCode createUrlResponseInfo(307), // statusCode
"http://redirect.location.com"); "http://redirect.location.com");
@ -920,12 +918,13 @@ public final class CronetDataSourceTest {
public Object answer(InvocationOnMock invocation) throws Throwable { public Object answer(InvocationOnMock invocation) throws Throwable {
if (!redirectCalled) { if (!redirectCalled) {
redirectCalled = true; redirectCalled = true;
dataSourceUnderTest.onRedirectReceived( dataSourceUnderTest.urlRequestCallback.onRedirectReceived(
mockUrlRequest, mockUrlRequest,
createUrlResponseInfoWithUrl("http://example.com/video", 300), createUrlResponseInfoWithUrl("http://example.com/video", 300),
"http://example.com/video/redirect"); "http://example.com/video/redirect");
} else { } else {
dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo); dataSourceUnderTest.urlRequestCallback.onResponseStarted(
mockUrlRequest, testUrlResponseInfo);
} }
return null; return null;
} }
@ -939,7 +938,8 @@ public final class CronetDataSourceTest {
new Answer<Object>() { new Answer<Object>() {
@Override @Override
public Object answer(InvocationOnMock invocation) throws Throwable { public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo); dataSourceUnderTest.urlRequestCallback.onResponseStarted(
mockUrlRequest, testUrlResponseInfo);
return null; return null;
} }
}) })
@ -952,7 +952,7 @@ public final class CronetDataSourceTest {
new Answer<Object>() { new Answer<Object>() {
@Override @Override
public Object answer(InvocationOnMock invocation) throws Throwable { public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onFailed( dataSourceUnderTest.urlRequestCallback.onFailed(
mockUrlRequest, mockUrlRequest,
createUrlResponseInfo(500), // statusCode createUrlResponseInfo(500), // statusCode
mockNetworkException); mockNetworkException);
@ -970,14 +970,15 @@ public final class CronetDataSourceTest {
@Override @Override
public Void answer(InvocationOnMock invocation) throws Throwable { public Void answer(InvocationOnMock invocation) throws Throwable {
if (positionAndRemaining[1] == 0) { if (positionAndRemaining[1] == 0) {
dataSourceUnderTest.onSucceeded(mockUrlRequest, testUrlResponseInfo); dataSourceUnderTest.urlRequestCallback.onSucceeded(
mockUrlRequest, testUrlResponseInfo);
} else { } else {
ByteBuffer inputBuffer = (ByteBuffer) invocation.getArguments()[0]; ByteBuffer inputBuffer = (ByteBuffer) invocation.getArguments()[0];
int readLength = Math.min(positionAndRemaining[1], inputBuffer.remaining()); int readLength = Math.min(positionAndRemaining[1], inputBuffer.remaining());
inputBuffer.put(buildTestDataBuffer(positionAndRemaining[0], readLength)); inputBuffer.put(buildTestDataBuffer(positionAndRemaining[0], readLength));
positionAndRemaining[0] += readLength; positionAndRemaining[0] += readLength;
positionAndRemaining[1] -= readLength; positionAndRemaining[1] -= readLength;
dataSourceUnderTest.onReadCompleted( dataSourceUnderTest.urlRequestCallback.onReadCompleted(
mockUrlRequest, testUrlResponseInfo, inputBuffer); mockUrlRequest, testUrlResponseInfo, inputBuffer);
} }
return null; return null;
@ -992,7 +993,7 @@ public final class CronetDataSourceTest {
new Answer<Object>() { new Answer<Object>() {
@Override @Override
public Object answer(InvocationOnMock invocation) throws Throwable { public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onFailed( dataSourceUnderTest.urlRequestCallback.onFailed(
mockUrlRequest, mockUrlRequest,
createUrlResponseInfo(500), // statusCode createUrlResponseInfo(500), // statusCode
mockNetworkException); mockNetworkException);

View file

@ -20,6 +20,8 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; 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.DataSourceException;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
@ -42,10 +44,8 @@ import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
/** /** An {@link HttpDataSource} that delegates to Square's {@link Call.Factory}. */
* An {@link HttpDataSource} that delegates to Square's {@link Call.Factory}. public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
*/
public class OkHttpDataSource implements HttpDataSource {
static { static {
ExoPlayerLibraryInfo.registerModule("goog.exo.okhttp"); ExoPlayerLibraryInfo.registerModule("goog.exo.okhttp");
@ -58,7 +58,6 @@ public class OkHttpDataSource implements HttpDataSource {
@Nullable private final String userAgent; @Nullable private final String userAgent;
@Nullable private final Predicate<String> contentTypePredicate; @Nullable private final Predicate<String> contentTypePredicate;
@Nullable private final TransferListener<? super OkHttpDataSource> listener;
@Nullable private final CacheControl cacheControl; @Nullable private final CacheControl cacheControl;
@Nullable private final RequestProperties defaultRequestProperties; @Nullable private final RequestProperties defaultRequestProperties;
@ -90,13 +89,15 @@ public class OkHttpDataSource implements HttpDataSource {
* by the source. * by the source.
* @param userAgent An optional User-Agent string. * @param userAgent An optional User-Agent string.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a {@link InvalidContentTypeException} is thrown from * predicate then a {@link InvalidContentTypeException} is thrown from {@link
* {@link #open(DataSpec)}. * #open(DataSpec)}.
* @param listener An optional listener. * @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 Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super OkHttpDataSource> listener) { @Nullable TransferListener<? super DataSource> listener) {
this(callFactory, userAgent, contentTypePredicate, listener, null, null); this(callFactory, userAgent, contentTypePredicate, listener, null, null);
} }
@ -105,24 +106,30 @@ public class OkHttpDataSource implements HttpDataSource {
* by the source. * by the source.
* @param userAgent An optional User-Agent string. * @param userAgent An optional User-Agent string.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a {@link InvalidContentTypeException} is thrown from * predicate then a {@link InvalidContentTypeException} is thrown from {@link
* {@link #open(DataSpec)}. * #open(DataSpec)}.
* @param listener An optional listener. * @param listener An optional listener.
* @param cacheControl An optional {@link CacheControl} for setting the Cache-Control header. * @param cacheControl An optional {@link CacheControl} for setting the Cache-Control header.
* @param defaultRequestProperties The optional default {@link RequestProperties} to be sent to * @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 Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super OkHttpDataSource> listener, @Nullable TransferListener<? super DataSource> listener,
@Nullable CacheControl cacheControl, @Nullable RequestProperties defaultRequestProperties) { @Nullable CacheControl cacheControl,
@Nullable RequestProperties defaultRequestProperties) {
super(DataSource.TYPE_REMOTE);
this.callFactory = Assertions.checkNotNull(callFactory); this.callFactory = Assertions.checkNotNull(callFactory);
this.userAgent = userAgent; this.userAgent = userAgent;
this.contentTypePredicate = contentTypePredicate; this.contentTypePredicate = contentTypePredicate;
this.listener = listener;
this.cacheControl = cacheControl; this.cacheControl = cacheControl;
this.defaultRequestProperties = defaultRequestProperties; this.defaultRequestProperties = defaultRequestProperties;
this.requestProperties = new RequestProperties(); this.requestProperties = new RequestProperties();
if (listener != null) {
addTransferListener(listener);
}
} }
@Override @Override
@ -203,9 +210,7 @@ public class OkHttpDataSource implements HttpDataSource {
} }
opened = true; opened = true;
if (listener != null) { transferStarted(dataSpec);
listener.onTransferStart(this, dataSpec);
}
return bytesToRead; return bytesToRead;
} }
@ -224,9 +229,7 @@ public class OkHttpDataSource implements HttpDataSource {
public void close() throws HttpDataSourceException { public void close() throws HttpDataSourceException {
if (opened) { if (opened) {
opened = false; opened = false;
if (listener != null) { transferEnded();
listener.onTransferEnd(this);
}
closeConnectionQuietly(); closeConnectionQuietly();
} }
} }
@ -333,9 +336,7 @@ public class OkHttpDataSource implements HttpDataSource {
throw new EOFException(); throw new EOFException();
} }
bytesSkipped += read; bytesSkipped += read;
if (listener != null) { bytesTransferred(read);
listener.onBytesTransferred(this, read);
}
} }
// Release the shared skip buffer. // Release the shared skip buffer.
@ -378,9 +379,7 @@ public class OkHttpDataSource implements HttpDataSource {
} }
bytesRead += read; bytesRead += read;
if (listener != null) { bytesTransferred(read);
listener.onBytesTransferred(this, read);
}
return read; return read;
} }

View file

@ -19,6 +19,7 @@ import android.net.Uri;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; 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.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.TransferListener; 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;
import net.butterflytv.rtmp_client.RtmpClient.RtmpIOException; import net.butterflytv.rtmp_client.RtmpClient.RtmpIOException;
/** /** A Real-Time Messaging Protocol (RTMP) {@link DataSource}. */
* A Real-Time Messaging Protocol (RTMP) {@link DataSource}. public final class RtmpDataSource extends BaseDataSource {
*/
public final class RtmpDataSource implements DataSource {
static { static {
ExoPlayerLibraryInfo.registerModule("goog.exo.rtmp"); ExoPlayerLibraryInfo.registerModule("goog.exo.rtmp");
} }
@Nullable private final TransferListener<? super RtmpDataSource> listener;
private RtmpClient rtmpClient; private RtmpClient rtmpClient;
private Uri uri; private Uri uri;
@ -44,11 +41,12 @@ public final class RtmpDataSource implements DataSource {
this(null); this(null);
} }
/** /** @param listener An optional listener. */
* @param listener An optional listener. public RtmpDataSource(@Nullable TransferListener<? super DataSource> listener) {
*/ super(DataSource.TYPE_REMOTE);
public RtmpDataSource(@Nullable TransferListener<? super RtmpDataSource> listener) { if (listener != null) {
this.listener = listener; addTransferListener(listener);
}
} }
@Override @Override
@ -57,9 +55,7 @@ public final class RtmpDataSource implements DataSource {
rtmpClient.open(dataSpec.uri.toString(), false); rtmpClient.open(dataSpec.uri.toString(), false);
this.uri = dataSpec.uri; this.uri = dataSpec.uri;
if (listener != null) { transferStarted(dataSpec);
listener.onTransferStart(this, dataSpec);
}
return C.LENGTH_UNSET; return C.LENGTH_UNSET;
} }
@ -69,9 +65,7 @@ public final class RtmpDataSource implements DataSource {
if (bytesRead == -1) { if (bytesRead == -1) {
return C.RESULT_END_OF_INPUT; return C.RESULT_END_OF_INPUT;
} }
if (listener != null) { bytesTransferred(bytesRead);
listener.onBytesTransferred(this, bytesRead);
}
return bytesRead; return bytesRead;
} }
@ -79,9 +73,7 @@ public final class RtmpDataSource implements DataSource {
public void close() { public void close() {
if (uri != null) { if (uri != null) {
uri = null; uri = null;
if (listener != null) { transferEnded();
listener.onTransferEnd(this);
}
} }
if (rtmpClient != null) { if (rtmpClient != null) {
rtmpClient.close(); rtmpClient.close();

View file

@ -25,17 +25,14 @@ import com.google.android.exoplayer2.upstream.TransferListener;
*/ */
public final class RtmpDataSourceFactory implements DataSource.Factory { public final class RtmpDataSourceFactory implements DataSource.Factory {
@Nullable private final @Nullable TransferListener<? super DataSource> listener;
private final TransferListener<? super RtmpDataSource> listener;
public RtmpDataSourceFactory() { public RtmpDataSourceFactory() {
this(null); this(null);
} }
/** /** @param listener An optional listener. */
* @param listener An optional listener. public RtmpDataSourceFactory(@Nullable TransferListener<? super DataSource> listener) {
*/
public RtmpDataSourceFactory(@Nullable TransferListener<? super RtmpDataSource> listener) {
this.listener = listener; this.listener = listener;
} }

View file

@ -18,15 +18,14 @@ package com.google.android.exoplayer2.upstream;
import android.content.Context; import android.content.Context;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
/** /** A {@link DataSource} for reading from a local asset. */
* A {@link DataSource} for reading from a local asset. public final class AssetDataSource extends BaseDataSource {
*/
public final class AssetDataSource implements DataSource {
/** /**
* Thrown when an {@link IOException} is encountered reading a local asset. * 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 AssetManager assetManager;
private final TransferListener<? super AssetDataSource> listener;
private Uri uri; private @Nullable Uri uri;
private InputStream inputStream; private @Nullable InputStream inputStream;
private long bytesRemaining; private long bytesRemaining;
private boolean opened; private boolean opened;
@ -58,9 +56,12 @@ public final class AssetDataSource implements DataSource {
* @param context A context. * @param context A context.
* @param listener An optional listener. * @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.assetManager = context.getAssets();
this.listener = listener; if (listener != null) {
addTransferListener(listener);
}
} }
@Override @Override
@ -96,9 +97,7 @@ public final class AssetDataSource implements DataSource {
} }
opened = true; opened = true;
if (listener != null) { transferStarted(dataSpec);
listener.onTransferStart(this, dataSpec);
}
return bytesRemaining; return bytesRemaining;
} }
@ -129,14 +128,12 @@ public final class AssetDataSource implements DataSource {
if (bytesRemaining != C.LENGTH_UNSET) { if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead; bytesRemaining -= bytesRead;
} }
if (listener != null) { bytesTransferred(bytesRead);
listener.onBytesTransferred(this, bytesRead);
}
return bytesRead; return bytesRead;
} }
@Override @Override
public Uri getUri() { public @Nullable Uri getUri() {
return uri; return uri;
} }
@ -153,9 +150,7 @@ public final class AssetDataSource implements DataSource {
inputStream = null; inputStream = null;
if (opened) { if (opened) {
opened = false; opened = false;
if (listener != null) { transferEnded();
listener.onTransferEnd(this);
}
} }
} }
} }

View file

@ -16,25 +16,26 @@
package com.google.android.exoplayer2.upstream; package com.google.android.exoplayer2.upstream;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
/** /** A {@link DataSource} for reading from a byte array. */
* A {@link DataSource} for reading from a byte array. public final class ByteArrayDataSource extends BaseDataSource {
*/
public final class ByteArrayDataSource implements DataSource {
private final byte[] data; private final byte[] data;
private Uri uri; private @Nullable Uri uri;
private int readPosition; private int readPosition;
private int bytesRemaining; private int bytesRemaining;
private boolean opened;
/** /**
* @param data The data to be read. * @param data The data to be read.
*/ */
public ByteArrayDataSource(byte[] data) { public ByteArrayDataSource(byte[] data) {
super(DataSource.TYPE_LOCAL);
Assertions.checkNotNull(data); Assertions.checkNotNull(data);
Assertions.checkArgument(data.length > 0); Assertions.checkArgument(data.length > 0);
this.data = data; this.data = data;
@ -50,6 +51,8 @@ public final class ByteArrayDataSource implements DataSource {
throw new IOException("Unsatisfiable range: [" + readPosition + ", " + dataSpec.length throw new IOException("Unsatisfiable range: [" + readPosition + ", " + dataSpec.length
+ "], length: " + data.length); + "], length: " + data.length);
} }
opened = true;
transferStarted(dataSpec);
return bytesRemaining; return bytesRemaining;
} }
@ -65,16 +68,21 @@ public final class ByteArrayDataSource implements DataSource {
System.arraycopy(data, readPosition, buffer, offset, readLength); System.arraycopy(data, readPosition, buffer, offset, readLength);
readPosition += readLength; readPosition += readLength;
bytesRemaining -= readLength; bytesRemaining -= readLength;
bytesTransferred(readLength);
return readLength; return readLength;
} }
@Override @Override
public Uri getUri() { public @Nullable Uri getUri() {
return uri; return uri;
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
if (opened) {
opened = false;
transferEnded();
}
uri = null; uri = null;
} }

View file

@ -19,6 +19,7 @@ import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.res.AssetFileDescriptor; import android.content.res.AssetFileDescriptor;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import java.io.EOFException; import java.io.EOFException;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -26,10 +27,8 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
/** /** A {@link DataSource} for reading from a content URI. */
* A {@link DataSource} for reading from a content URI. public final class ContentDataSource extends BaseDataSource {
*/
public final class ContentDataSource implements DataSource {
/** /**
* Thrown when an {@link IOException} is encountered reading from a content URI. * 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 ContentResolver resolver;
private final TransferListener<? super ContentDataSource> listener;
private Uri uri; private @Nullable Uri uri;
private AssetFileDescriptor assetFileDescriptor; private @Nullable AssetFileDescriptor assetFileDescriptor;
private FileInputStream inputStream; private @Nullable FileInputStream inputStream;
private long bytesRemaining; private long bytesRemaining;
private boolean opened; private boolean opened;
@ -62,9 +60,13 @@ public final class ContentDataSource implements DataSource {
* @param context A context. * @param context A context.
* @param listener An optional listener. * @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.resolver = context.getContentResolver();
this.listener = listener; if (listener != null) {
addTransferListener(listener);
}
} }
@Override @Override
@ -102,9 +104,7 @@ public final class ContentDataSource implements DataSource {
} }
opened = true; opened = true;
if (listener != null) { transferStarted(dataSpec);
listener.onTransferStart(this, dataSpec);
}
return bytesRemaining; return bytesRemaining;
} }
@ -136,14 +136,12 @@ public final class ContentDataSource implements DataSource {
if (bytesRemaining != C.LENGTH_UNSET) { if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead; bytesRemaining -= bytesRead;
} }
if (listener != null) { bytesTransferred(bytesRead);
listener.onBytesTransferred(this, bytesRead);
}
return bytesRead; return bytesRead;
} }
@Override @Override
public Uri getUri() { public @Nullable Uri getUri() {
return uri; return uri;
} }
@ -168,9 +166,7 @@ public final class ContentDataSource implements DataSource {
assetFileDescriptor = null; assetFileDescriptor = null;
if (opened) { if (opened) {
opened = false; opened = false;
if (listener != null) { transferEnded();
listener.onTransferEnd(this);
}
} }
} }
} }

View file

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.upstream; package com.google.android.exoplayer2.upstream;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import android.util.Base64; import android.util.Base64;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
@ -23,16 +24,18 @@ import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.net.URLDecoder; import java.net.URLDecoder;
/** /** A {@link DataSource} for reading data URLs, as defined by RFC 2397. */
* A {@link DataSource} for reading data URLs, as defined by RFC 2397. public final class DataSchemeDataSource extends BaseDataSource {
*/
public final class DataSchemeDataSource implements DataSource {
public static final String SCHEME_DATA = "data"; public static final String SCHEME_DATA = "data";
private DataSpec dataSpec; private @Nullable DataSpec dataSpec;
private int bytesRead; private int bytesRead;
private byte[] data; private @Nullable byte[] data;
public DataSchemeDataSource() {
super(DataSource.TYPE_LOCAL);
}
@Override @Override
public long open(DataSpec dataSpec) throws IOException { public long open(DataSpec dataSpec) throws IOException {
@ -57,6 +60,7 @@ public final class DataSchemeDataSource implements DataSource {
// TODO: Add support for other charsets. // TODO: Add support for other charsets.
data = URLDecoder.decode(dataString, C.ASCII_NAME).getBytes(); data = URLDecoder.decode(dataString, C.ASCII_NAME).getBytes();
} }
transferStarted(dataSpec);
return data.length; return data.length;
} }
@ -72,18 +76,22 @@ public final class DataSchemeDataSource implements DataSource {
readLength = Math.min(readLength, remainingBytes); readLength = Math.min(readLength, remainingBytes);
System.arraycopy(data, bytesRead, buffer, offset, readLength); System.arraycopy(data, bytesRead, buffer, offset, readLength);
bytesRead += readLength; bytesRead += readLength;
bytesTransferred(readLength);
return readLength; return readLength;
} }
@Override @Override
public Uri getUri() { public @Nullable Uri getUri() {
return dataSpec != null ? dataSpec.uri : null; return dataSpec != null ? dataSpec.uri : null;
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
if (data != null) {
data = null;
transferEnded();
}
dataSpec = null; dataSpec = null;
data = null;
} }
} }

View file

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.upstream; package com.google.android.exoplayer2.upstream;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
@ -41,13 +42,13 @@ import java.util.regex.Pattern;
/** /**
* An {@link HttpDataSource} that uses Android's {@link HttpURLConnection}. * An {@link HttpDataSource} that uses Android's {@link HttpURLConnection}.
* <p> *
* By default this implementation will not follow cross-protocol redirects (i.e. redirects from * <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 * HTTP to HTTPS or vice versa). Cross-protocol redirects can be enabled by using the {@link
* {@link #DefaultHttpDataSource(String, Predicate, TransferListener, int, int, boolean, * #DefaultHttpDataSource(String, Predicate, TransferListener, int, int, boolean,
* RequestProperties)} constructor and passing {@code true} as the second last argument. * 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. * The default connection timeout, in milliseconds.
@ -69,14 +70,13 @@ public class DefaultHttpDataSource implements HttpDataSource {
private final int connectTimeoutMillis; private final int connectTimeoutMillis;
private final int readTimeoutMillis; private final int readTimeoutMillis;
private final String userAgent; private final String userAgent;
private final Predicate<String> contentTypePredicate; private final @Nullable Predicate<String> contentTypePredicate;
private final RequestProperties defaultRequestProperties; private final @Nullable RequestProperties defaultRequestProperties;
private final RequestProperties requestProperties; private final RequestProperties requestProperties;
private final TransferListener<? super DefaultHttpDataSource> listener;
private DataSpec dataSpec; private @Nullable DataSpec dataSpec;
private HttpURLConnection connection; private @Nullable HttpURLConnection connection;
private InputStream inputStream; private @Nullable InputStream inputStream;
private boolean opened; private boolean opened;
private long bytesToSkip; private long bytesToSkip;
@ -88,22 +88,24 @@ public class DefaultHttpDataSource implements HttpDataSource {
/** /**
* @param userAgent The User-Agent string that should be used. * @param userAgent The User-Agent string that should be used.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from * predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link
* {@link #open(DataSpec)}. * #open(DataSpec)}.
*/ */
public DefaultHttpDataSource(String userAgent, Predicate<String> contentTypePredicate) { public DefaultHttpDataSource(String userAgent, @Nullable Predicate<String> contentTypePredicate) {
this(userAgent, contentTypePredicate, null); this(userAgent, contentTypePredicate, null);
} }
/** /**
* @param userAgent The User-Agent string that should be used. * @param userAgent The User-Agent string that should be used.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from * predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link
* {@link #open(DataSpec)}. * #open(DataSpec)}.
* @param listener An optional listener. * @param listener An optional listener.
*/ */
public DefaultHttpDataSource(String userAgent, Predicate<String> contentTypePredicate, public DefaultHttpDataSource(
TransferListener<? super DefaultHttpDataSource> listener) { String userAgent,
@Nullable Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super DataSource> listener) {
this(userAgent, contentTypePredicate, listener, DEFAULT_CONNECT_TIMEOUT_MILLIS, this(userAgent, contentTypePredicate, listener, DEFAULT_CONNECT_TIMEOUT_MILLIS,
DEFAULT_READ_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 userAgent The User-Agent string that should be used.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from * predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link
* {@link #open(DataSpec)}. * #open(DataSpec)}.
* @param listener An optional listener. * @param listener An optional listener.
* @param connectTimeoutMillis The connection timeout, in milliseconds. A timeout of zero is * @param connectTimeoutMillis The connection timeout, in milliseconds. A timeout of zero is
* interpreted as an infinite timeout. * interpreted as an infinite timeout.
* @param readTimeoutMillis The read timeout, in milliseconds. A timeout of zero is interpreted * @param readTimeoutMillis The read timeout, in milliseconds. A timeout of zero is interpreted as
* as an infinite timeout. * an infinite timeout.
*/ */
public DefaultHttpDataSource(String userAgent, Predicate<String> contentTypePredicate, public DefaultHttpDataSource(
TransferListener<? super DefaultHttpDataSource> listener, int connectTimeoutMillis, String userAgent,
@Nullable Predicate<String> contentTypePredicate,
@Nullable TransferListener<? super DataSource> listener,
int connectTimeoutMillis,
int readTimeoutMillis) { int readTimeoutMillis) {
this(userAgent, contentTypePredicate, listener, connectTimeoutMillis, readTimeoutMillis, false, this(userAgent, contentTypePredicate, listener, connectTimeoutMillis, readTimeoutMillis, false,
null); null);
@ -129,35 +134,42 @@ public class DefaultHttpDataSource implements HttpDataSource {
/** /**
* @param userAgent The User-Agent string that should be used. * @param userAgent The User-Agent string that should be used.
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
* predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from * predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link
* {@link #open(DataSpec)}. * #open(DataSpec)}.
* @param listener An optional listener. * @param listener An optional listener.
* @param connectTimeoutMillis The connection timeout, in milliseconds. A timeout of zero is * @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 * interpreted as an infinite timeout. Pass {@link #DEFAULT_CONNECT_TIMEOUT_MILLIS} to use the
* the default value. * default value.
* @param readTimeoutMillis The read timeout, in milliseconds. A timeout of zero is interpreted * @param readTimeoutMillis The read timeout, in milliseconds. A timeout of zero is interpreted as
* as an infinite timeout. Pass {@link #DEFAULT_READ_TIMEOUT_MILLIS} to use the default value. * 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 * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP
* to HTTPS and vice versa) are enabled. * to HTTPS and vice versa) are enabled.
* @param defaultRequestProperties The default request properties to be sent to the server as * @param defaultRequestProperties The default request properties to be sent to the server as HTTP
* HTTP headers or {@code null} if not required. * headers or {@code null} if not required.
*/ */
public DefaultHttpDataSource(String userAgent, Predicate<String> contentTypePredicate, public DefaultHttpDataSource(
TransferListener<? super DefaultHttpDataSource> listener, int connectTimeoutMillis, String userAgent,
int readTimeoutMillis, boolean allowCrossProtocolRedirects, @Nullable Predicate<String> contentTypePredicate,
RequestProperties defaultRequestProperties) { @Nullable TransferListener<? super DataSource> listener,
int connectTimeoutMillis,
int readTimeoutMillis,
boolean allowCrossProtocolRedirects,
@Nullable RequestProperties defaultRequestProperties) {
super(DataSource.TYPE_REMOTE);
this.userAgent = Assertions.checkNotEmpty(userAgent); this.userAgent = Assertions.checkNotEmpty(userAgent);
this.contentTypePredicate = contentTypePredicate; this.contentTypePredicate = contentTypePredicate;
this.listener = listener;
this.requestProperties = new RequestProperties(); this.requestProperties = new RequestProperties();
this.connectTimeoutMillis = connectTimeoutMillis; this.connectTimeoutMillis = connectTimeoutMillis;
this.readTimeoutMillis = readTimeoutMillis; this.readTimeoutMillis = readTimeoutMillis;
this.allowCrossProtocolRedirects = allowCrossProtocolRedirects; this.allowCrossProtocolRedirects = allowCrossProtocolRedirects;
this.defaultRequestProperties = defaultRequestProperties; this.defaultRequestProperties = defaultRequestProperties;
if (listener != null) {
addTransferListener(listener);
}
} }
@Override @Override
public Uri getUri() { public @Nullable Uri getUri() {
return connection == null ? null : Uri.parse(connection.getURL().toString()); return connection == null ? null : Uri.parse(connection.getURL().toString());
} }
@ -254,9 +266,7 @@ public class DefaultHttpDataSource implements HttpDataSource {
} }
opened = true; opened = true;
if (listener != null) { transferStarted(dataSpec);
listener.onTransferStart(this, dataSpec);
}
return bytesToRead; return bytesToRead;
} }
@ -287,9 +297,7 @@ public class DefaultHttpDataSource implements HttpDataSource {
closeConnectionQuietly(); closeConnectionQuietly();
if (opened) { if (opened) {
opened = false; opened = false;
if (listener != null) { transferEnded();
listener.onTransferEnd(this);
}
} }
} }
} }
@ -534,9 +542,7 @@ public class DefaultHttpDataSource implements HttpDataSource {
throw new EOFException(); throw new EOFException();
} }
bytesSkipped += read; bytesSkipped += read;
if (listener != null) { bytesTransferred(read);
listener.onBytesTransferred(this, read);
}
} }
// Release the shared skip buffer. // Release the shared skip buffer.
@ -579,9 +585,7 @@ public class DefaultHttpDataSource implements HttpDataSource {
} }
bytesRead += read; bytesRead += read;
if (listener != null) { bytesTransferred(read);
listener.onBytesTransferred(this, read);
}
return read; return read;
} }

View file

@ -16,15 +16,14 @@
package com.google.android.exoplayer2.upstream; package com.google.android.exoplayer2.upstream;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
/** /** A {@link DataSource} for reading local files. */
* A {@link DataSource} for reading local files. public final class FileDataSource extends BaseDataSource {
*/
public final class FileDataSource implements DataSource {
/** /**
* Thrown when IOException is encountered during local file read operation. * 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 @Nullable RandomAccessFile file;
private @Nullable Uri uri;
private RandomAccessFile file;
private Uri uri;
private long bytesRemaining; private long bytesRemaining;
private boolean opened; private boolean opened;
@ -48,11 +45,12 @@ public final class FileDataSource implements DataSource {
this(null); this(null);
} }
/** /** @param listener An optional listener. */
* @param listener An optional listener. public FileDataSource(@Nullable TransferListener<? super DataSource> listener) {
*/ super(DataSource.TYPE_LOCAL);
public FileDataSource(TransferListener<? super FileDataSource> listener) { if (listener != null) {
this.listener = listener; addTransferListener(listener);
}
} }
@Override @Override
@ -71,9 +69,7 @@ public final class FileDataSource implements DataSource {
} }
opened = true; opened = true;
if (listener != null) { transferStarted(dataSpec);
listener.onTransferStart(this, dataSpec);
}
return bytesRemaining; return bytesRemaining;
} }
@ -94,9 +90,7 @@ public final class FileDataSource implements DataSource {
if (bytesRead > 0) { if (bytesRead > 0) {
bytesRemaining -= bytesRead; bytesRemaining -= bytesRead;
if (listener != null) { bytesTransferred(bytesRead);
listener.onBytesTransferred(this, bytesRead);
}
} }
return bytesRead; return bytesRead;
@ -104,7 +98,7 @@ public final class FileDataSource implements DataSource {
} }
@Override @Override
public Uri getUri() { public @Nullable Uri getUri() {
return uri; return uri;
} }
@ -121,9 +115,7 @@ public final class FileDataSource implements DataSource {
file = null; file = null;
if (opened) { if (opened) {
opened = false; opened = false;
if (listener != null) { transferEnded();
listener.onTransferEnd(this);
}
} }
} }
} }

View file

@ -15,18 +15,20 @@
*/ */
package com.google.android.exoplayer2.upstream; package com.google.android.exoplayer2.upstream;
import android.support.annotation.Nullable;
/** /**
* A {@link DataSource.Factory} that produces {@link FileDataSource}. * A {@link DataSource.Factory} that produces {@link FileDataSource}.
*/ */
public final class FileDataSourceFactory implements DataSource.Factory { public final class FileDataSourceFactory implements DataSource.Factory {
private final TransferListener<? super FileDataSource> listener; private final @Nullable TransferListener<? super DataSource> listener;
public FileDataSourceFactory() { public FileDataSourceFactory() {
this(null); this(null);
} }
public FileDataSourceFactory(TransferListener<? super FileDataSource> listener) { public FileDataSourceFactory(@Nullable TransferListener<? super DataSource> listener) {
this.listener = listener; this.listener = listener;
} }

View file

@ -19,6 +19,7 @@ import android.content.Context;
import android.content.res.AssetFileDescriptor; import android.content.res.AssetFileDescriptor;
import android.content.res.Resources; import android.content.res.Resources;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import java.io.EOFException; import java.io.EOFException;
@ -28,12 +29,12 @@ import java.io.InputStream;
/** /**
* A {@link DataSource} for reading a raw resource inside the APK. * 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 * rawResourceId is the integer identifier of a raw resource. {@link #buildRawResourceUri(int)} can
* be used to build {@link Uri}s in this format. * 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. * 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"; public static final String RAW_RESOURCE_SCHEME = "rawresource";
private final Resources resources; private final Resources resources;
private final TransferListener<? super RawResourceDataSource> listener;
private Uri uri; private @Nullable Uri uri;
private AssetFileDescriptor assetFileDescriptor; private @Nullable AssetFileDescriptor assetFileDescriptor;
private InputStream inputStream; private @Nullable InputStream inputStream;
private long bytesRemaining; private long bytesRemaining;
private boolean opened; private boolean opened;
@ -81,10 +81,13 @@ public final class RawResourceDataSource implements DataSource {
* @param context A context. * @param context A context.
* @param listener An optional listener. * @param listener An optional listener.
*/ */
public RawResourceDataSource(Context context, public RawResourceDataSource(
TransferListener<? super RawResourceDataSource> listener) { Context context, @Nullable TransferListener<? super DataSource> listener) {
super(DataSource.TYPE_LOCAL);
this.resources = context.getResources(); this.resources = context.getResources();
this.listener = listener; if (listener != null) {
addTransferListener(listener);
}
} }
@Override @Override
@ -124,9 +127,7 @@ public final class RawResourceDataSource implements DataSource {
} }
opened = true; opened = true;
if (listener != null) { transferStarted(dataSpec);
listener.onTransferStart(this, dataSpec);
}
return bytesRemaining; return bytesRemaining;
} }
@ -158,14 +159,12 @@ public final class RawResourceDataSource implements DataSource {
if (bytesRemaining != C.LENGTH_UNSET) { if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead; bytesRemaining -= bytesRead;
} }
if (listener != null) { bytesTransferred(bytesRead);
listener.onBytesTransferred(this, bytesRead);
}
return bytesRead; return bytesRead;
} }
@Override @Override
public Uri getUri() { public @Nullable Uri getUri() {
return uri; return uri;
} }
@ -190,9 +189,7 @@ public final class RawResourceDataSource implements DataSource {
assetFileDescriptor = null; assetFileDescriptor = null;
if (opened) { if (opened) {
opened = false; opened = false;
if (listener != null) { transferEnded();
listener.onTransferEnd(this);
}
} }
} }
} }

View file

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.upstream; package com.google.android.exoplayer2.upstream;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
@ -25,10 +26,8 @@ import java.net.InetSocketAddress;
import java.net.MulticastSocket; import java.net.MulticastSocket;
import java.net.SocketException; import java.net.SocketException;
/** /** A UDP {@link DataSource}. */
* A UDP {@link DataSource}. public final class UdpDataSource extends BaseDataSource {
*/
public final class UdpDataSource implements DataSource {
/** /**
* Thrown when an error is encountered when trying to read from a {@link UdpDataSource}. * 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; public static final int DEAFULT_SOCKET_TIMEOUT_MILLIS = 8 * 1000;
private final TransferListener<? super UdpDataSource> listener;
private final int socketTimeoutMillis; private final int socketTimeoutMillis;
private final byte[] packetBuffer; private final byte[] packetBuffer;
private final DatagramPacket packet; private final DatagramPacket packet;
private Uri uri; private @Nullable Uri uri;
private DatagramSocket socket; private @Nullable DatagramSocket socket;
private MulticastSocket multicastSocket; private @Nullable MulticastSocket multicastSocket;
private InetAddress address; private @Nullable InetAddress address;
private InetSocketAddress socketAddress; private @Nullable InetSocketAddress socketAddress;
private boolean opened; private boolean opened;
private int packetRemaining; private int packetRemaining;
/** /** @param listener An optional listener. */
* @param listener An optional listener. public UdpDataSource(@Nullable TransferListener<? super DataSource> listener) {
*/
public UdpDataSource(TransferListener<? super UdpDataSource> listener) {
this(listener, DEFAULT_MAX_PACKET_SIZE); this(listener, DEFAULT_MAX_PACKET_SIZE);
} }
@ -76,7 +72,7 @@ public final class UdpDataSource implements DataSource {
* @param listener An optional listener. * @param listener An optional listener.
* @param maxPacketSize The maximum datagram packet size, in bytes. * @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); 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 * @param socketTimeoutMillis The socket timeout in milliseconds. A timeout of zero is interpreted
* as an infinite timeout. * as an infinite timeout.
*/ */
public UdpDataSource(TransferListener<? super UdpDataSource> listener, int maxPacketSize, public UdpDataSource(
@Nullable TransferListener<? super DataSource> listener,
int maxPacketSize,
int socketTimeoutMillis) { int socketTimeoutMillis) {
this.listener = listener; super(DataSource.TYPE_REMOTE);
this.socketTimeoutMillis = socketTimeoutMillis; this.socketTimeoutMillis = socketTimeoutMillis;
packetBuffer = new byte[maxPacketSize]; packetBuffer = new byte[maxPacketSize];
packet = new DatagramPacket(packetBuffer, 0, maxPacketSize); packet = new DatagramPacket(packetBuffer, 0, maxPacketSize);
if (listener != null) {
addTransferListener(listener);
}
} }
@Override @Override
@ -121,9 +122,7 @@ public final class UdpDataSource implements DataSource {
} }
opened = true; opened = true;
if (listener != null) { transferStarted(dataSpec);
listener.onTransferStart(this, dataSpec);
}
return C.LENGTH_UNSET; return C.LENGTH_UNSET;
} }
@ -141,9 +140,7 @@ public final class UdpDataSource implements DataSource {
throw new UdpDataSourceException(e); throw new UdpDataSourceException(e);
} }
packetRemaining = packet.getLength(); packetRemaining = packet.getLength();
if (listener != null) { bytesTransferred(packetRemaining);
listener.onBytesTransferred(this, packetRemaining);
}
} }
int packetOffset = packet.getLength() - packetRemaining; int packetOffset = packet.getLength() - packetRemaining;
@ -154,7 +151,7 @@ public final class UdpDataSource implements DataSource {
} }
@Override @Override
public Uri getUri() { public @Nullable Uri getUri() {
return uri; return uri;
} }
@ -178,9 +175,7 @@ public final class UdpDataSource implements DataSource {
packetRemaining = 0; packetRemaining = 0;
if (opened) { if (opened) {
opened = false; opened = false;
if (listener != null) { transferEnded();
listener.onTransferEnd(this);
}
} }
} }

View file

@ -20,6 +20,7 @@ import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData; import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData;
import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData.Segment; 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.DataSource;
import com.google.android.exoplayer2.upstream.DataSourceException; import com.google.android.exoplayer2.upstream.DataSourceException;
import com.google.android.exoplayer2.upstream.DataSpec; 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} * A fake {@link DataSource} capable of simulating various scenarios. It uses a {@link FakeDataSet}
* instance which determines the response to data access calls. * 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}. * Factory to create a {@link FakeDataSource}.
*/ */
public static class Factory implements DataSource.Factory { public static class Factory implements DataSource.Factory {
protected final TransferListener<? super FakeDataSource> transferListener; protected final TransferListener<? super DataSource> transferListener;
protected FakeDataSet fakeDataSet; 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.transferListener = transferListener;
this.dataSourceType = DataSource.TYPE_LOCAL;
} }
public final Factory setFakeDataSet(FakeDataSet fakeDataSet) { public final Factory setFakeDataSet(FakeDataSet fakeDataSet) {
@ -51,19 +54,23 @@ public class FakeDataSource implements DataSource {
return this; return this;
} }
@Override public final Factory setDataSourceType(@DataSource.Type int dataSourceType) {
public DataSource createDataSource() { this.dataSourceType = dataSourceType;
return new FakeDataSource(fakeDataSet, transferListener); return this;
} }
@Override
public DataSource createDataSource() {
return new FakeDataSource(fakeDataSet, transferListener, dataSourceType);
}
} }
private final FakeDataSet fakeDataSet; private final FakeDataSet fakeDataSet;
private final TransferListener<? super FakeDataSource> transferListener;
private final ArrayList<DataSpec> openedDataSpecs; private final ArrayList<DataSpec> openedDataSpecs;
private Uri uri; private Uri uri;
private boolean opened; private boolean openCalled;
private boolean sourceOpened;
private FakeData fakeData; private FakeData fakeData;
private int currentSegmentIndex; private int currentSegmentIndex;
private long bytesRemaining; private long bytesRemaining;
@ -73,15 +80,20 @@ public class FakeDataSource implements DataSource {
} }
public FakeDataSource(FakeDataSet fakeDataSet) { public FakeDataSource(FakeDataSet fakeDataSet) {
this(fakeDataSet, null); this(fakeDataSet, null, DataSource.TYPE_LOCAL);
} }
public FakeDataSource(FakeDataSet fakeDataSet, public FakeDataSource(
@Nullable TransferListener<? super FakeDataSource> transferListener) { FakeDataSet fakeDataSet,
@Nullable TransferListener<? super DataSource> transferListener,
@DataSource.Type int dataSourceType) {
super(dataSourceType);
Assertions.checkNotNull(fakeDataSet); Assertions.checkNotNull(fakeDataSet);
this.fakeDataSet = fakeDataSet; this.fakeDataSet = fakeDataSet;
this.transferListener = transferListener;
this.openedDataSpecs = new ArrayList<>(); this.openedDataSpecs = new ArrayList<>();
if (transferListener != null) {
addTransferListener(transferListener);
}
} }
public final FakeDataSet getDataSet() { public final FakeDataSet getDataSet() {
@ -90,9 +102,9 @@ public class FakeDataSource implements DataSource {
@Override @Override
public final long open(DataSpec dataSpec) throws IOException { 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. // DataSpec requires a matching close call even if open fails.
opened = true;
uri = dataSpec.uri; uri = dataSpec.uri;
openedDataSpecs.add(dataSpec); openedDataSpecs.add(dataSpec);
@ -129,9 +141,8 @@ public class FakeDataSource implements DataSource {
currentSegmentIndex++; currentSegmentIndex++;
} }
} }
if (transferListener != null) { sourceOpened = true;
transferListener.onTransferStart(this, dataSpec); transferStarted(dataSpec);
}
// Configure bytesRemaining, and return. // Configure bytesRemaining, and return.
if (dataSpec.length == C.LENGTH_UNSET) { if (dataSpec.length == C.LENGTH_UNSET) {
bytesRemaining = totalLength - dataSpec.position; bytesRemaining = totalLength - dataSpec.position;
@ -144,7 +155,7 @@ public class FakeDataSource implements DataSource {
@Override @Override
public final int read(byte[] buffer, int offset, int readLength) throws IOException { public final int read(byte[] buffer, int offset, int readLength) throws IOException {
Assertions.checkState(opened); Assertions.checkState(sourceOpened);
while (true) { while (true) {
if (currentSegmentIndex == fakeData.getSegments().size() || bytesRemaining == 0) { if (currentSegmentIndex == fakeData.getSegments().size() || bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT; return C.RESULT_END_OF_INPUT;
@ -171,9 +182,7 @@ public class FakeDataSource implements DataSource {
System.arraycopy(current.data, current.bytesRead, buffer, offset, readLength); System.arraycopy(current.data, current.bytesRead, buffer, offset, readLength);
} }
onDataRead(readLength); onDataRead(readLength);
if (transferListener != null) { bytesTransferred(readLength);
transferListener.onBytesTransferred(this, readLength);
}
bytesRemaining -= readLength; bytesRemaining -= readLength;
current.bytesRead += readLength; current.bytesRead += readLength;
if (current.bytesRead == current.length) { if (current.bytesRead == current.length) {
@ -191,8 +200,8 @@ public class FakeDataSource implements DataSource {
@Override @Override
public final void close() throws IOException { public final void close() throws IOException {
Assertions.checkState(opened); Assertions.checkState(openCalled);
opened = false; openCalled = false;
uri = null; uri = null;
if (fakeData != null && currentSegmentIndex < fakeData.getSegments().size()) { if (fakeData != null && currentSegmentIndex < fakeData.getSegments().size()) {
Segment current = fakeData.getSegments().get(currentSegmentIndex); Segment current = fakeData.getSegments().get(currentSegmentIndex);
@ -200,8 +209,9 @@ public class FakeDataSource implements DataSource {
current.exceptionCleared = true; current.exceptionCleared = true;
} }
} }
if (transferListener != null) { if (sourceOpened) {
transferListener.onTransferEnd(this); sourceOpened = false;
transferEnded();
} }
fakeData = null; fakeData = null;
} }
@ -219,7 +229,7 @@ public class FakeDataSource implements DataSource {
/** Returns whether the data source is currently opened. */ /** Returns whether the data source is currently opened. */
public final boolean isOpened() { public final boolean isOpened() {
return opened; return sourceOpened;
} }
protected void onDataRead(int bytesRead) throws IOException { protected void onDataRead(int bytesRead) throws IOException {