From 06a644eccd95fb2c67ac87ef96b9eff83bfe3644 Mon Sep 17 00:00:00 2001 From: eguven Date: Fri, 2 Sep 2016 05:36:49 -0700 Subject: [PATCH] DataSourceException: Used to specify a DataSource error. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=132053698 --- .../ext/cronet/CronetDataSource.java | 8 +++- .../ext/okhttp/OkHttpDataSource.java | 8 +++- .../upstream/cache/CacheDataSourceTest.java | 25 +++++------ .../exoplayer2/upstream/DataSource.java | 3 +- .../upstream/DataSourceException.java | 41 +++++++++++++++++++ .../upstream/DefaultHttpDataSource.java | 7 +++- .../upstream/cache/CacheDataSource.java | 36 +++++++++++----- .../exoplayer2/testutil/FakeDataSource.java | 9 +--- 8 files changed, 100 insertions(+), 37 deletions(-) create mode 100644 library/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java diff --git a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java index 9e4c463c87..401941addc 100644 --- a/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java +++ b/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java @@ -20,6 +20,7 @@ import android.os.ConditionVariable; import android.text.TextUtils; import android.util.Log; 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.HttpDataSource; 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. int responseCode = info.getHttpStatusCode(); 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. try { diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java index 91896556f7..e6ecd77d02 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.okhttp; import android.net.Uri; 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.HttpDataSource; import com.google.android.exoplayer2.upstream.TransferListener; @@ -162,7 +163,12 @@ public class OkHttpDataSource implements HttpDataSource { if (!response.isSuccessful()) { Map> headers = request.headers().toMultimap(); 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. diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java index 789c8bb413..5e85ad4d4c 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CacheDataSourceTest.java @@ -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.TestUtil; import com.google.android.exoplayer2.upstream.DataSpec; -import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; + import java.io.File; import java.io.IOException; import java.util.Arrays; @@ -55,7 +55,7 @@ public class CacheDataSourceTest extends InstrumentationTestCase { } public void testMaxCacheFileSize() throws Exception { - CacheDataSource cacheDataSource = createCacheDataSource(false, false, false); + CacheDataSource cacheDataSource = createCacheDataSource(false, false); assertReadDataContentLength(cacheDataSource, false, false); assertEquals((int) Math.ceil((double) TEST_DATA.length / MAX_CACHE_FILE_SIZE), 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 // 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); // If the user try to access off range then it should throw an IOException try { - cacheDataSource = createCacheDataSource(false, false, false); + cacheDataSource = createCacheDataSource(false, false); cacheDataSource.open(new DataSpec(Uri.EMPTY, TEST_DATA.length, 5, KEY_1)); fail(); - } catch (TestIOException e) { + } catch (IOException e) { // success } } public void testContentLengthEdgeCases() throws Exception { // 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); assertEquals(C.LENGTH_UNSET, simpleCache.getContentLength(KEY_1)); // 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. - cacheDataSource = createCacheDataSource(true, false, true); + cacheDataSource = createCacheDataSource(false, true); assertReadDataContentLength(cacheDataSource, true, true); // 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) throws IOException { // Read all data from upstream and cache - CacheDataSource cacheDataSource = createCacheDataSource(false, false, simulateUnknownLength); + CacheDataSource cacheDataSource = createCacheDataSource(false, simulateUnknownLength); assertReadDataContentLength(cacheDataSource, unboundedRequest, simulateUnknownLength); // Just read from cache - cacheDataSource = createCacheDataSource(false, true, simulateUnknownLength); + cacheDataSource = createCacheDataSource(true, simulateUnknownLength); assertReadDataContentLength(cacheDataSource, unboundedRequest, false /*length is already cached*/); } @@ -168,7 +168,7 @@ public class CacheDataSourceTest extends InstrumentationTestCase { cacheDataSource.close(); } - private CacheDataSource createCacheDataSource(boolean set416exception, boolean setReadException, + private CacheDataSource createCacheDataSource(boolean setReadException, boolean simulateUnknownLength) { Builder builder = new Builder(); if (setReadException) { @@ -177,14 +177,9 @@ public class CacheDataSourceTest extends InstrumentationTestCase { builder.setSimulateUnknownLength(simulateUnknownLength); builder.appendReadData(TEST_DATA); FakeDataSource upstream = builder.build(); - upstream.setUnsatisfiableRangeException(set416exception - ? new InvalidResponseCodeException(416, null, null) - : new TestIOException()); return new CacheDataSource(simpleCache, upstream, CacheDataSource.FLAG_BLOCK_ON_CACHE | CacheDataSource.FLAG_CACHE_UNBOUNDED_REQUESTS, MAX_CACHE_FILE_SIZE); } - private static class TestIOException extends IOException {} - } diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java b/library/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java index 16333b2e4d..2b445fc05b 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/DataSource.java @@ -43,7 +43,8 @@ public interface DataSource { * that any partial effects of the invocation are cleaned up. * * @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 * (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 diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java b/library/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java new file mode 100644 index 0000000000..e6b3ae2707 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/DataSourceException.java @@ -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; + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index 1365311a75..bcb4b9d2bd 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -209,7 +209,12 @@ public class DefaultHttpDataSource implements HttpDataSource { if (responseCode < 200 || responseCode > 299) { Map> headers = connection.getHeaderFields(); 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. diff --git a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java b/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java index 72e8df3ce3..0cd7d54564 100644 --- a/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/upstream/cache/CacheDataSource.java @@ -20,9 +20,9 @@ import android.util.Log; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.upstream.DataSink; 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.FileDataSource; -import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; import com.google.android.exoplayer2.upstream.TeeDataSource; import com.google.android.exoplayer2.upstream.cache.CacheDataSink.CacheDataSinkException; import java.io.IOException; @@ -184,6 +184,9 @@ public final class CacheDataSource implements DataSource { @Override public int read(byte[] buffer, int offset, int max) throws IOException { + if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } try { int bytesRead = currentDataSource.read(buffer, offset, max); if (bytesRead >= 0) { @@ -287,23 +290,34 @@ public final class CacheDataSource implements DataSource { currentRequestUnbounded = dataSpec.length == C.LENGTH_UNSET; boolean successful = false; - long currentBytesRemaining; + long currentBytesRemaining = 0; try { currentBytesRemaining = currentDataSource.open(dataSpec); successful = true; - } catch (InvalidResponseCodeException e) { - // if this isn't the initial open call (we had read some bytes) and got an 'unsatisfiable - // byte-range' (416) response for an unbounded range request then mute the exception. We are - // trying to find the stream end. - if (!initial && e.responseCode == 416 && currentRequestUnbounded) { - currentBytesRemaining = 0; - } else { + } catch (IOException e) { + // if this isn't the initial open call (we had read some bytes) and an unbounded range request + // failed because of POSITION_OUT_OF_RANGE then mute the exception. We are trying to find the + // end of the stream. + if (!initial && currentRequestUnbounded) { + Throwable cause = e; + 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; } } - // If we did an unbounded request (which means bytesRemaining == C.LENGTH_UNSET) and got a - // resolved length from open() request + // If we did an unbounded request (which means it's to upstream and + // bytesRemaining == C.LENGTH_UNSET) and got a resolved length from open() request if (currentRequestUnbounded && currentBytesRemaining != C.LENGTH_UNSET) { bytesRemaining = currentBytesRemaining; // If writing into cache diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java index 7a5a0d3773..37947bb1ab 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeDataSource.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.testutil; import android.net.Uri; import com.google.android.exoplayer2.C; 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.util.Assertions; import java.io.IOException; @@ -49,7 +50,6 @@ public final class FakeDataSource implements DataSource { private boolean opened; private int currentSegmentIndex; private long bytesRemaining; - private IOException unsatisfiableRangeException; private FakeDataSource(boolean simulateUnknownLength, ArrayList segments) { this.simulateUnknownLength = simulateUnknownLength; @@ -60,7 +60,6 @@ public final class FakeDataSource implements DataSource { } this.totalLength = totalLength; openedDataSpecs = new ArrayList<>(); - unsatisfiableRangeException = new IOException("Unsatisfiable range"); } @Override @@ -73,7 +72,7 @@ public final class FakeDataSource implements DataSource { // If the source knows that the request is unsatisfiable then fail. if (dataSpec.position >= totalLength || (dataSpec.length != C.LENGTH_UNSET && (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. boolean findingCurrentSegmentIndex = true; @@ -160,10 +159,6 @@ public final class FakeDataSource implements DataSource { return dataSpecs; } - public void setUnsatisfiableRangeException(IOException unsatisfiableRangeException) { - this.unsatisfiableRangeException = unsatisfiableRangeException; - } - private static class Segment { public final IOException exception;