DataSourceException: Used to specify a DataSource error.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=132053698
This commit is contained in:
eguven 2016-09-02 05:36:49 -07:00 committed by Oliver Woodman
parent 884bcb649e
commit 06a644eccd
8 changed files with 100 additions and 37 deletions

View File

@ -20,6 +20,7 @@ import android.os.ConditionVariable;
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.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;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
@ -329,7 +330,12 @@ public class CronetDataSource extends UrlRequest.Callback implements HttpDataSou
// Check for a valid response code. // Check for a valid response code.
int responseCode = info.getHttpStatusCode(); int responseCode = info.getHttpStatusCode();
if (responseCode < 200 || responseCode > 299) { if (responseCode < 200 || responseCode > 299) {
throw new InvalidResponseCodeException(responseCode, info.getAllHeaders(), currentDataSpec); InvalidResponseCodeException exception = new InvalidResponseCodeException(
responseCode, info.getAllHeaders(), currentDataSpec);
if (responseCode == 416) {
exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE));
}
throw exception;
} }
// Check for a valid content type. // Check for a valid content type.
try { try {

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.okhttp;
import android.net.Uri; import android.net.Uri;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
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;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
@ -162,7 +163,12 @@ public class OkHttpDataSource implements HttpDataSource {
if (!response.isSuccessful()) { if (!response.isSuccessful()) {
Map<String, List<String>> headers = request.headers().toMultimap(); Map<String, List<String>> headers = request.headers().toMultimap();
closeConnectionQuietly(); closeConnectionQuietly();
throw new InvalidResponseCodeException(responseCode, headers, dataSpec); InvalidResponseCodeException exception = new InvalidResponseCodeException(
responseCode, headers, dataSpec);
if (responseCode == 416) {
exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE));
}
throw exception;
} }
// Check for a valid content type. // Check for a valid content type.

View File

@ -23,7 +23,7 @@ import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.testutil.FakeDataSource.Builder; import com.google.android.exoplayer2.testutil.FakeDataSource.Builder;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@ -55,7 +55,7 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
} }
public void testMaxCacheFileSize() throws Exception { public void testMaxCacheFileSize() throws Exception {
CacheDataSource cacheDataSource = createCacheDataSource(false, false, false); CacheDataSource cacheDataSource = createCacheDataSource(false, false);
assertReadDataContentLength(cacheDataSource, false, false); assertReadDataContentLength(cacheDataSource, false, false);
assertEquals((int) Math.ceil((double) TEST_DATA.length / MAX_CACHE_FILE_SIZE), assertEquals((int) Math.ceil((double) TEST_DATA.length / MAX_CACHE_FILE_SIZE),
cacheDir.listFiles().length); cacheDir.listFiles().length);
@ -85,28 +85,28 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
// Now do an unbounded request. This will read all of the data from cache and then try to read // Now do an unbounded request. This will read all of the data from cache and then try to read
// more from upstream which will cause to a 416 so CDS will store the length. // more from upstream which will cause to a 416 so CDS will store the length.
CacheDataSource cacheDataSource = createCacheDataSource(true, true, true); CacheDataSource cacheDataSource = createCacheDataSource(true, true);
assertReadDataContentLength(cacheDataSource, true, true); assertReadDataContentLength(cacheDataSource, true, true);
// If the user try to access off range then it should throw an IOException // If the user try to access off range then it should throw an IOException
try { try {
cacheDataSource = createCacheDataSource(false, false, false); cacheDataSource = createCacheDataSource(false, false);
cacheDataSource.open(new DataSpec(Uri.EMPTY, TEST_DATA.length, 5, KEY_1)); cacheDataSource.open(new DataSpec(Uri.EMPTY, TEST_DATA.length, 5, KEY_1));
fail(); fail();
} catch (TestIOException e) { } catch (IOException e) {
// success // success
} }
} }
public void testContentLengthEdgeCases() throws Exception { public void testContentLengthEdgeCases() throws Exception {
// Read partial at EOS but don't cross it so length is unknown // Read partial at EOS but don't cross it so length is unknown
CacheDataSource cacheDataSource = createCacheDataSource(false, false, true); CacheDataSource cacheDataSource = createCacheDataSource(false, true);
assertReadData(cacheDataSource, true, TEST_DATA.length - 2, 2); assertReadData(cacheDataSource, true, TEST_DATA.length - 2, 2);
assertEquals(C.LENGTH_UNSET, simpleCache.getContentLength(KEY_1)); assertEquals(C.LENGTH_UNSET, simpleCache.getContentLength(KEY_1));
// Now do an unbounded request for whole data. This will cause a bounded request from upstream. // Now do an unbounded request for whole data. This will cause a bounded request from upstream.
// End of data from upstream shouldn't be mixed up with EOS and cause length set wrong. // End of data from upstream shouldn't be mixed up with EOS and cause length set wrong.
cacheDataSource = createCacheDataSource(true, false, true); cacheDataSource = createCacheDataSource(false, true);
assertReadDataContentLength(cacheDataSource, true, true); assertReadDataContentLength(cacheDataSource, true, true);
// Now the length set correctly do an unbounded request with offset // Now the length set correctly do an unbounded request with offset
@ -121,11 +121,11 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
private void assertCacheAndRead(boolean unboundedRequest, boolean simulateUnknownLength) private void assertCacheAndRead(boolean unboundedRequest, boolean simulateUnknownLength)
throws IOException { throws IOException {
// Read all data from upstream and cache // Read all data from upstream and cache
CacheDataSource cacheDataSource = createCacheDataSource(false, false, simulateUnknownLength); CacheDataSource cacheDataSource = createCacheDataSource(false, simulateUnknownLength);
assertReadDataContentLength(cacheDataSource, unboundedRequest, simulateUnknownLength); assertReadDataContentLength(cacheDataSource, unboundedRequest, simulateUnknownLength);
// Just read from cache // Just read from cache
cacheDataSource = createCacheDataSource(false, true, simulateUnknownLength); cacheDataSource = createCacheDataSource(true, simulateUnknownLength);
assertReadDataContentLength(cacheDataSource, unboundedRequest, assertReadDataContentLength(cacheDataSource, unboundedRequest,
false /*length is already cached*/); false /*length is already cached*/);
} }
@ -168,7 +168,7 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
cacheDataSource.close(); cacheDataSource.close();
} }
private CacheDataSource createCacheDataSource(boolean set416exception, boolean setReadException, private CacheDataSource createCacheDataSource(boolean setReadException,
boolean simulateUnknownLength) { boolean simulateUnknownLength) {
Builder builder = new Builder(); Builder builder = new Builder();
if (setReadException) { if (setReadException) {
@ -177,14 +177,9 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
builder.setSimulateUnknownLength(simulateUnknownLength); builder.setSimulateUnknownLength(simulateUnknownLength);
builder.appendReadData(TEST_DATA); builder.appendReadData(TEST_DATA);
FakeDataSource upstream = builder.build(); FakeDataSource upstream = builder.build();
upstream.setUnsatisfiableRangeException(set416exception
? new InvalidResponseCodeException(416, null, null)
: new TestIOException());
return new CacheDataSource(simpleCache, upstream, return new CacheDataSource(simpleCache, upstream,
CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_CACHE_UNBOUNDED_REQUESTS, CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_CACHE_UNBOUNDED_REQUESTS,
MAX_CACHE_FILE_SIZE); MAX_CACHE_FILE_SIZE);
} }
private static class TestIOException extends IOException {}
} }

View File

@ -43,7 +43,8 @@ public interface DataSource {
* that any partial effects of the invocation are cleaned up. * that any partial effects of the invocation are cleaned up.
* *
* @param dataSpec Defines the data to be read. * @param dataSpec Defines the data to be read.
* @throws IOException If an error occurs opening the source. * @throws IOException If an error occurs opening the source. {@link DataSourceException} can be
* thrown or used as a cause of the thrown exception to specify the reason of the error.
* @return The number of bytes that can be read from the opened source. For unbounded requests * @return The number of bytes that can be read from the opened source. For unbounded requests
* (i.e. requests where {@link DataSpec#length} equals {@link C#LENGTH_UNSET}) this value * (i.e. requests where {@link DataSpec#length} equals {@link C#LENGTH_UNSET}) this value
* is the resolved length of the request, or {@link C#LENGTH_UNSET} if the length is still * is the resolved length of the request, or {@link C#LENGTH_UNSET} if the length is still

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.upstream;
import java.io.IOException;
/**
* Used to specify reason of a DataSource error.
*/
public final class DataSourceException extends IOException {
public static final int POSITION_OUT_OF_RANGE = 0;
/**
* The reason of this {@link DataSourceException}. It can only be {@link #POSITION_OUT_OF_RANGE}.
*/
public final int reason;
/**
* Constructs a DataSourceException.
*
* @param reason Reason of the error. It can only be {@link #POSITION_OUT_OF_RANGE}.
*/
public DataSourceException(int reason) {
this.reason = reason;
}
}

View File

@ -209,7 +209,12 @@ public class DefaultHttpDataSource implements HttpDataSource {
if (responseCode < 200 || responseCode > 299) { if (responseCode < 200 || responseCode > 299) {
Map<String, List<String>> headers = connection.getHeaderFields(); Map<String, List<String>> headers = connection.getHeaderFields();
closeConnectionQuietly(); closeConnectionQuietly();
throw new InvalidResponseCodeException(responseCode, headers, dataSpec); InvalidResponseCodeException exception =
new InvalidResponseCodeException(responseCode, headers, dataSpec);
if (responseCode == 416) {
exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE));
}
throw exception;
} }
// Check for a valid content type. // Check for a valid content type.

View File

@ -20,9 +20,9 @@ import android.util.Log;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSink; import com.google.android.exoplayer2.upstream.DataSink;
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.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.FileDataSource; import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException;
import com.google.android.exoplayer2.upstream.TeeDataSource; import com.google.android.exoplayer2.upstream.TeeDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSink.CacheDataSinkException; import com.google.android.exoplayer2.upstream.cache.CacheDataSink.CacheDataSinkException;
import java.io.IOException; import java.io.IOException;
@ -184,6 +184,9 @@ public final class CacheDataSource implements DataSource {
@Override @Override
public int read(byte[] buffer, int offset, int max) throws IOException { public int read(byte[] buffer, int offset, int max) throws IOException {
if (bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT;
}
try { try {
int bytesRead = currentDataSource.read(buffer, offset, max); int bytesRead = currentDataSource.read(buffer, offset, max);
if (bytesRead >= 0) { if (bytesRead >= 0) {
@ -287,23 +290,34 @@ public final class CacheDataSource implements DataSource {
currentRequestUnbounded = dataSpec.length == C.LENGTH_UNSET; currentRequestUnbounded = dataSpec.length == C.LENGTH_UNSET;
boolean successful = false; boolean successful = false;
long currentBytesRemaining; long currentBytesRemaining = 0;
try { try {
currentBytesRemaining = currentDataSource.open(dataSpec); currentBytesRemaining = currentDataSource.open(dataSpec);
successful = true; successful = true;
} catch (InvalidResponseCodeException e) { } catch (IOException e) {
// if this isn't the initial open call (we had read some bytes) and got an 'unsatisfiable // if this isn't the initial open call (we had read some bytes) and an unbounded range request
// byte-range' (416) response for an unbounded range request then mute the exception. We are // failed because of POSITION_OUT_OF_RANGE then mute the exception. We are trying to find the
// trying to find the stream end. // end of the stream.
if (!initial && e.responseCode == 416 && currentRequestUnbounded) { if (!initial && currentRequestUnbounded) {
currentBytesRemaining = 0; Throwable cause = e;
} else { while (cause != null) {
if (cause instanceof DataSourceException) {
int reason = ((DataSourceException) cause).reason;
if (reason == DataSourceException.POSITION_OUT_OF_RANGE) {
e = null;
break;
}
}
cause = cause.getCause();
}
}
if (e != null) {
throw e; throw e;
} }
} }
// If we did an unbounded request (which means bytesRemaining == C.LENGTH_UNSET) and got a // If we did an unbounded request (which means it's to upstream and
// resolved length from open() request // bytesRemaining == C.LENGTH_UNSET) and got a resolved length from open() request
if (currentRequestUnbounded && currentBytesRemaining != C.LENGTH_UNSET) { if (currentRequestUnbounded && currentBytesRemaining != C.LENGTH_UNSET) {
bytesRemaining = currentBytesRemaining; bytesRemaining = currentBytesRemaining;
// If writing into cache // If writing into cache

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.testutil;
import android.net.Uri; import android.net.Uri;
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.DataSource;
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.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
@ -49,7 +50,6 @@ public final class FakeDataSource implements DataSource {
private boolean opened; private boolean opened;
private int currentSegmentIndex; private int currentSegmentIndex;
private long bytesRemaining; private long bytesRemaining;
private IOException unsatisfiableRangeException;
private FakeDataSource(boolean simulateUnknownLength, ArrayList<Segment> segments) { private FakeDataSource(boolean simulateUnknownLength, ArrayList<Segment> segments) {
this.simulateUnknownLength = simulateUnknownLength; this.simulateUnknownLength = simulateUnknownLength;
@ -60,7 +60,6 @@ public final class FakeDataSource implements DataSource {
} }
this.totalLength = totalLength; this.totalLength = totalLength;
openedDataSpecs = new ArrayList<>(); openedDataSpecs = new ArrayList<>();
unsatisfiableRangeException = new IOException("Unsatisfiable range");
} }
@Override @Override
@ -73,7 +72,7 @@ public final class FakeDataSource implements DataSource {
// If the source knows that the request is unsatisfiable then fail. // If the source knows that the request is unsatisfiable then fail.
if (dataSpec.position >= totalLength || (dataSpec.length != C.LENGTH_UNSET if (dataSpec.position >= totalLength || (dataSpec.length != C.LENGTH_UNSET
&& (dataSpec.position + dataSpec.length > totalLength))) { && (dataSpec.position + dataSpec.length > totalLength))) {
throw (IOException) unsatisfiableRangeException.fillInStackTrace(); throw new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE);
} }
// Scan through the segments, configuring them for the current read. // Scan through the segments, configuring them for the current read.
boolean findingCurrentSegmentIndex = true; boolean findingCurrentSegmentIndex = true;
@ -160,10 +159,6 @@ public final class FakeDataSource implements DataSource {
return dataSpecs; return dataSpecs;
} }
public void setUnsatisfiableRangeException(IOException unsatisfiableRangeException) {
this.unsatisfiableRangeException = unsatisfiableRangeException;
}
private static class Segment { private static class Segment {
public final IOException exception; public final IOException exception;