From df49f90b7fb564c7bda7823bad84a83818f98198 Mon Sep 17 00:00:00 2001 From: claincly Date: Fri, 30 Jul 2021 13:43:22 +0100 Subject: [PATCH] Simplify the error code handling. PiperOrigin-RevId: 387786273 --- .../ext/okhttp/OkHttpDataSource.java | 58 +++++++------ .../upstream/DefaultHttpDataSource.java | 80 +++++++++++++----- .../exoplayer2/upstream/AssetDataSource.java | 2 +- .../upstream/ContentDataSource.java | 37 ++++---- .../upstream/RawResourceDataSource.java | 84 +++++++++++-------- 5 files changed, 160 insertions(+), 101 deletions(-) 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 a04097a89f..82aedb0cf8 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 @@ -358,16 +358,10 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { transferStarted(dataSpec); try { - if (!skipFully(bytesToSkip)) { - throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); - } - } catch (IOException e) { + skipFully(bytesToSkip, dataSpec); + } catch (HttpDataSourceException e) { closeConnectionQuietly(); - throw new HttpDataSourceException( - e, - dataSpec, - PlaybackException.ERROR_CODE_IO_UNSPECIFIED, - HttpDataSourceException.TYPE_OPEN); + throw e; } return bytesToRead; @@ -452,29 +446,43 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { * Attempts to skip the specified number of bytes in full. * * @param bytesToSkip The number of bytes to skip. - * @throws InterruptedIOException If the thread is interrupted during the operation. - * @throws IOException If an error occurs reading from the source. - * @return Whether the bytes were skipped in full. If {@code false} then the data ended before the - * specified number of bytes were skipped. Always {@code true} if {@code bytesToSkip == 0}. + * @param dataSpec The {@link DataSpec}. + * @throws HttpDataSourceException If the thread is interrupted during the operation, or an error + * occurs while reading from the source, or if the data ended before skipping the specified + * number of bytes. */ - private boolean skipFully(long bytesToSkip) throws IOException { + private void skipFully(long bytesToSkip, DataSpec dataSpec) throws HttpDataSourceException { if (bytesToSkip == 0) { - return true; + return; } byte[] skipBuffer = new byte[4096]; - while (bytesToSkip > 0) { - int readLength = (int) min(bytesToSkip, skipBuffer.length); - int read = castNonNull(responseByteStream).read(skipBuffer, 0, readLength); - if (Thread.currentThread().isInterrupted()) { - throw new InterruptedIOException(); + try { + while (bytesToSkip > 0) { + int readLength = (int) min(bytesToSkip, skipBuffer.length); + int read = castNonNull(responseByteStream).read(skipBuffer, 0, readLength); + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedIOException(); + } + if (read == -1) { + throw new HttpDataSourceException( + dataSpec, + PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, + HttpDataSourceException.TYPE_OPEN); + } + bytesToSkip -= read; + bytesTransferred(read); } - if (read == -1) { - return false; + return; + } catch (IOException e) { + if (e instanceof HttpDataSourceException) { + throw (HttpDataSourceException) e; + } else { + throw new HttpDataSourceException( + dataSpec, + PlaybackException.ERROR_CODE_IO_UNSPECIFIED, + HttpDataSourceException.TYPE_OPEN); } - bytesToSkip -= read; - bytesTransferred(read); } - return true; } /** diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index d7f366d59b..cbc5dc044d 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -37,8 +37,8 @@ import java.io.InterruptedIOException; import java.io.OutputStream; import java.lang.reflect.Method; import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.NoRouteToHostException; -import java.net.ProtocolException; import java.net.URL; import java.util.Collections; import java.util.HashMap; @@ -459,11 +459,13 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou transferStarted(dataSpec); try { - if (!skipFully(bytesToSkip)) { - throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); - } + skipFully(bytesToSkip, dataSpec); } catch (IOException e) { closeConnectionQuietly(); + + if (e instanceof HttpDataSourceException) { + throw (HttpDataSourceException) e; + } throw new HttpDataSourceException( e, dataSpec, @@ -562,7 +564,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou || responseCode == HTTP_STATUS_TEMPORARY_REDIRECT || responseCode == HTTP_STATUS_PERMANENT_REDIRECT)) { connection.disconnect(); - url = handleRedirect(url, location); + url = handleRedirect(url, location, dataSpec); } else if (httpMethod == DataSpec.HTTP_METHOD_POST && (responseCode == HttpURLConnection.HTTP_MULT_CHOICE || responseCode == HttpURLConnection.HTTP_MOVED_PERM @@ -576,14 +578,18 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou httpMethod = DataSpec.HTTP_METHOD_GET; httpBody = null; } - url = handleRedirect(url, location); + url = handleRedirect(url, location, dataSpec); } else { return connection; } } // If we get here we've been redirected more times than are permitted. - throw new NoRouteToHostException("Too many redirects: " + redirectCount); + throw new HttpDataSourceException( + new NoRouteToHostException("Too many redirects: " + redirectCount), + dataSpec, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, + HttpDataSourceException.TYPE_OPEN); } /** @@ -658,27 +664,50 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou * * @param originalUrl The original URL. * @param location The Location header in the response. May be {@code null}. + * @param dataSpec The {@link DataSpec}. * @return The next URL. - * @throws IOException If redirection isn't possible. + * @throws HttpDataSourceException If redirection isn't possible. */ - private URL handleRedirect(URL originalUrl, @Nullable String location) throws IOException { + private URL handleRedirect(URL originalUrl, @Nullable String location, DataSpec dataSpec) + throws HttpDataSourceException { if (location == null) { - throw new ProtocolException("Null location redirect"); + throw new HttpDataSourceException( + "Null location redirect", + dataSpec, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, + HttpDataSourceException.TYPE_OPEN); } // Form the new url. - URL url = new URL(originalUrl, location); + URL url; + try { + url = new URL(originalUrl, location); + } catch (MalformedURLException e) { + throw new HttpDataSourceException( + e, + dataSpec, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, + HttpDataSourceException.TYPE_OPEN); + } + // Check that the protocol of the new url is supported. String protocol = url.getProtocol(); if (!"https".equals(protocol) && !"http".equals(protocol)) { - throw new ProtocolException("Unsupported protocol redirect: " + protocol); + throw new HttpDataSourceException( + "Unsupported protocol redirect: " + protocol, + dataSpec, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, + HttpDataSourceException.TYPE_OPEN); } if (!allowCrossProtocolRedirects && !protocol.equals(originalUrl.getProtocol())) { - throw new ProtocolException( + throw new HttpDataSourceException( "Disallowed cross-protocol redirect (" + originalUrl.getProtocol() + " to " + protocol - + ")"); + + ")", + dataSpec, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, + HttpDataSourceException.TYPE_OPEN); } return url; } @@ -687,29 +716,34 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou * Attempts to skip the specified number of bytes in full. * * @param bytesToSkip The number of bytes to skip. - * @throws InterruptedIOException If the thread is interrupted during the operation. - * @throws IOException If an error occurs reading from the source. - * @return Whether the bytes were skipped in full. If {@code false} then the data ended before the - * specified number of bytes were skipped. Always {@code true} if {@code bytesToSkip == 0}. + * @param dataSpec The {@link DataSpec}. + * @throws IOException If the thread is interrupted during the operation, or if the data ended + * before skipping the specified number of bytes. */ - private boolean skipFully(long bytesToSkip) throws IOException { + private void skipFully(long bytesToSkip, DataSpec dataSpec) throws IOException { if (bytesToSkip == 0) { - return true; + return; } byte[] skipBuffer = new byte[4096]; while (bytesToSkip > 0) { int readLength = (int) min(bytesToSkip, skipBuffer.length); int read = castNonNull(inputStream).read(skipBuffer, 0, readLength); if (Thread.currentThread().isInterrupted()) { - throw new InterruptedIOException(); + throw new HttpDataSourceException( + new InterruptedIOException(), + dataSpec, + PlaybackException.ERROR_CODE_IO_UNSPECIFIED, + HttpDataSourceException.TYPE_OPEN); } if (read == -1) { - return false; + throw new HttpDataSourceException( + dataSpec, + PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, + HttpDataSourceException.TYPE_OPEN); } bytesToSkip -= read; bytesTransferred(read); } - return true; } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java index 660b41abed..f295fc5722 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/AssetDataSource.java @@ -83,7 +83,7 @@ public final class AssetDataSource extends BaseDataSource { // assetManager.open() returns an AssetInputStream, whose skip() implementation only skips // fewer bytes than requested if the skip is beyond the end of the asset's data. throw new AssetDataSourceException( - /* cause=*/ null, PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); + /* cause= */ null, PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); } if (dataSpec.length != C.LENGTH_UNSET) { bytesRemaining = dataSpec.length; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java index 550b938dec..3e53896ac0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/ContentDataSource.java @@ -39,17 +39,12 @@ public final class ContentDataSource extends BaseDataSource { /** @deprecated Use {@link #ContentDataSourceException(IOException, int)}. */ @Deprecated public ContentDataSourceException(IOException cause) { - super(cause, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); + this(cause, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); } - /** - * Creates a new instance. - * - * @param cause The error cause. - * @param errorCode See {@link PlaybackException.ErrorCode}. - */ + /** Creates a new instance. */ public ContentDataSourceException( - IOException cause, @PlaybackException.ErrorCode int errorCode) { + @Nullable IOException cause, @PlaybackException.ErrorCode int errorCode) { super(cause, errorCode); } } @@ -78,7 +73,10 @@ public final class ContentDataSource extends BaseDataSource { AssetFileDescriptor assetFileDescriptor = resolver.openAssetFileDescriptor(uri, "r"); this.assetFileDescriptor = assetFileDescriptor; if (assetFileDescriptor == null) { - throw new FileNotFoundException("Could not open file descriptor for: " + uri); + // openAssetFileDescriptor returns null if the provider recently crashed. + throw new ContentDataSourceException( + new IOException("Could not open file descriptor for: " + uri), + PlaybackException.ERROR_CODE_IO_UNSPECIFIED); } long assetFileDescriptorLength = assetFileDescriptor.getLength(); @@ -93,7 +91,8 @@ public final class ContentDataSource extends BaseDataSource { // file. if (assetFileDescriptorLength != AssetFileDescriptor.UNKNOWN_LENGTH && dataSpec.position > assetFileDescriptorLength) { - throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); + throw new ContentDataSourceException( + /* cause= */ null, PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); } long assetFileDescriptorOffset = assetFileDescriptor.getStartOffset(); long skipped = @@ -102,7 +101,8 @@ public final class ContentDataSource extends BaseDataSource { if (skipped != dataSpec.position) { // We expect the skip to be satisfied in full. If it isn't then we're probably trying to // read beyond the end of the last resource in the file. - throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); + throw new ContentDataSourceException( + /* cause= */ null, PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); } if (assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH) { // The asset must extend to the end of the file. We can try and resolve the length with @@ -115,18 +115,25 @@ public final class ContentDataSource extends BaseDataSource { bytesRemaining = channelSize - channel.position(); if (bytesRemaining < 0) { // The skip above was satisfied in full, but skipped beyond the end of the file. - throw new DataSourceException( - PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); + throw new ContentDataSourceException( + /* cause= */ null, PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); } } } else { bytesRemaining = assetFileDescriptorLength - skipped; if (bytesRemaining < 0) { - throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); + throw new ContentDataSourceException( + /* cause= */ null, PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); } } + } catch (ContentDataSourceException e) { + throw e; } catch (IOException e) { - throw new ContentDataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); + throw new ContentDataSourceException( + e, + e instanceof FileNotFoundException + ? PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND + : PlaybackException.ERROR_CODE_IO_UNSPECIFIED); } if (dataSpec.length != C.LENGTH_UNSET) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java index cb97f21479..d896acef2f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java @@ -56,37 +56,24 @@ public final class RawResourceDataSource extends BaseDataSource { /** Thrown when an {@link IOException} is encountered reading from a raw resource. */ public static class RawResourceDataSourceException extends DataSourceException { - /** @deprecated Use {@link #RawResourceDataSourceException(String, int)}. */ + /** @deprecated Use {@link #RawResourceDataSourceException(String, Throwable, int)}. */ @Deprecated public RawResourceDataSourceException(String message) { - super(message, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); + super(message, /* cause= */ null, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); } - /** - * Creates a new instance. - * - * @param message The error message. - * @param errorCode See {@link PlaybackException.ErrorCode}. - */ - public RawResourceDataSourceException( - String message, @PlaybackException.ErrorCode int errorCode) { - super(message, errorCode); - } - - /** @deprecated Use {@link #RawResourceDataSourceException(Throwable, int)}. */ + /** @deprecated Use {@link #RawResourceDataSourceException(String, Throwable, int)}. */ @Deprecated - public RawResourceDataSourceException(Throwable e) { - super(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); + public RawResourceDataSourceException(Throwable cause) { + super(cause, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); } - /** - * Creates a new instance. - * - * @param e The error cause. - * @param errorCode See {@link PlaybackException.ErrorCode}. - */ - public RawResourceDataSourceException(Throwable e, @PlaybackException.ErrorCode int errorCode) { - super(e, errorCode); + /** Creates a new instance. */ + public RawResourceDataSourceException( + @Nullable String message, + @Nullable Throwable cause, + @PlaybackException.ErrorCode int errorCode) { + super(message, cause, errorCode); } } @@ -134,7 +121,8 @@ public final class RawResourceDataSource extends BaseDataSource { } catch (NumberFormatException e) { throw new RawResourceDataSourceException( "Resource identifier must be an integer.", - PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND); + /* cause= */ null, + PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK); } } else if (TextUtils.equals(ContentResolver.SCHEME_ANDROID_RESOURCE, uri.getScheme())) { String path = Assertions.checkNotNull(uri.getPath()); @@ -148,7 +136,9 @@ public final class RawResourceDataSource extends BaseDataSource { resourceName, /* defType= */ "raw", /* defPackage= */ packageName); if (resourceId == 0) { throw new RawResourceDataSourceException( - "Resource not found.", PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND); + "Resource not found.", + /* cause= */ null, + PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND); } } else { throw new RawResourceDataSourceException( @@ -156,7 +146,8 @@ public final class RawResourceDataSource extends BaseDataSource { + RAW_RESOURCE_SCHEME + " or " + ContentResolver.SCHEME_ANDROID_RESOURCE, - PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND); + /* cause= */ null, + PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK); } transferInitializing(dataSpec); @@ -165,13 +156,16 @@ public final class RawResourceDataSource extends BaseDataSource { try { assetFileDescriptor = resources.openRawResourceFd(resourceId); } catch (Resources.NotFoundException e) { - throw new RawResourceDataSourceException(e, PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND); + throw new RawResourceDataSourceException( + /* message= */ null, e, PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND); } this.assetFileDescriptor = assetFileDescriptor; if (assetFileDescriptor == null) { throw new RawResourceDataSourceException( - "Resource is compressed: " + uri, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); + "Resource is compressed: " + uri, + /* cause= */ null, + PlaybackException.ERROR_CODE_IO_UNSPECIFIED); } long assetFileDescriptorLength = assetFileDescriptor.getLength(); @@ -187,7 +181,10 @@ public final class RawResourceDataSource extends BaseDataSource { // extends to the end of the file. if (assetFileDescriptorLength != AssetFileDescriptor.UNKNOWN_LENGTH && dataSpec.position > assetFileDescriptorLength) { - throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); + throw new RawResourceDataSourceException( + /* message= */ null, + /* cause= */ null, + PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); } long assetFileDescriptorOffset = assetFileDescriptor.getStartOffset(); long skipped = @@ -196,7 +193,10 @@ public final class RawResourceDataSource extends BaseDataSource { if (skipped != dataSpec.position) { // We expect the skip to be satisfied in full. If it isn't then we're probably trying to // read beyond the end of the last resource in the file. - throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); + throw new RawResourceDataSourceException( + /* message= */ null, + /* cause= */ null, + PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); } if (assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH) { // The asset must extend to the end of the file. We can try and resolve the length with @@ -208,7 +208,9 @@ public final class RawResourceDataSource extends BaseDataSource { bytesRemaining = channel.size() - channel.position(); if (bytesRemaining < 0) { // The skip above was satisfied in full, but skipped beyond the end of the file. - throw new DataSourceException( + throw new RawResourceDataSourceException( + /* message= */ null, + /* cause= */ null, PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); } } @@ -218,8 +220,11 @@ public final class RawResourceDataSource extends BaseDataSource { throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE); } } + } catch (RawResourceDataSourceException e) { + throw e; } catch (IOException e) { - throw new RawResourceDataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); + throw new RawResourceDataSourceException( + /* message= */ null, e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); } if (dataSpec.length != C.LENGTH_UNSET) { @@ -245,14 +250,17 @@ public final class RawResourceDataSource extends BaseDataSource { bytesRemaining == C.LENGTH_UNSET ? length : (int) min(bytesRemaining, length); bytesRead = castNonNull(inputStream).read(buffer, offset, bytesToRead); } catch (IOException e) { - throw new RawResourceDataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); + throw new RawResourceDataSourceException( + /* message= */ null, e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); } if (bytesRead == -1) { if (bytesRemaining != C.LENGTH_UNSET) { // End of stream reached having not read sufficient data. throw new RawResourceDataSourceException( - new EOFException(), PlaybackException.ERROR_CODE_IO_UNSPECIFIED); + "End of stream reached having not read sufficient data.", + new EOFException(), + PlaybackException.ERROR_CODE_IO_UNSPECIFIED); } return C.RESULT_END_OF_INPUT; } @@ -278,7 +286,8 @@ public final class RawResourceDataSource extends BaseDataSource { inputStream.close(); } } catch (IOException e) { - throw new RawResourceDataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); + throw new RawResourceDataSourceException( + /* message= */ null, e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); } finally { inputStream = null; try { @@ -286,7 +295,8 @@ public final class RawResourceDataSource extends BaseDataSource { assetFileDescriptor.close(); } } catch (IOException e) { - throw new RawResourceDataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); + throw new RawResourceDataSourceException( + /* message= */ null, e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); } finally { assetFileDescriptor = null; if (opened) {