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 ddf503be92..483df638b3 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 @@ -669,12 +669,11 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { if (message != null && Ascii.toLowerCase(message).contains("err_cleartext_not_permitted")) { throw new CleartextNotPermittedException(connectionOpenException, dataSpec); } - @PlaybackException.ErrorCode - int errorCode = - getErrorCodeForException( - connectionOpenException, /* occurredWhileOpeningConnection*/ true); throw new OpenException( - connectionOpenException, dataSpec, errorCode, getStatus(urlRequest)); + connectionOpenException, + dataSpec, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, + getStatus(urlRequest)); } else if (!connectionOpened) { // The timeout was reached before the connection was opened. throw new OpenException( @@ -685,10 +684,13 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { } } catch (InterruptedException e) { Thread.currentThread().interrupt(); + // An interruption means the operation is being cancelled, in which case this exception should + // not cause the player to fail. If it does, it likely means that the owner of the operation + // is failing to swallow the interruption, which makes us enter an invalid state. throw new OpenException( new InterruptedIOException(), dataSpec, - PlaybackException.ERROR_CODE_IO_UNSPECIFIED, + PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK, Status.INVALID); } @@ -1029,7 +1031,9 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { throw new OpenException( e, dataSpec, - getErrorCodeForException(e, /* occurredWhileOpeningConnection= */ false), + e instanceof SocketTimeoutException + ? PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT + : PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, Status.READING_RESPONSE); } } @@ -1099,11 +1103,8 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { if (exception instanceof HttpDataSourceException) { throw (HttpDataSourceException) exception; } else { - throw new HttpDataSourceException( - exception, - dataSpec, - getErrorCodeForException(exception, /* occurredWhileOpeningConnection= */ false), - HttpDataSourceException.TYPE_READ); + throw HttpDataSourceException.createForIOException( + exception, dataSpec, HttpDataSourceException.TYPE_READ); } } } @@ -1116,27 +1117,6 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { return readBuffer; } - @PlaybackException.ErrorCode - private static int getErrorCodeForException( - IOException exception, boolean occurredWhileOpeningConnection) { - if (exception instanceof UnknownHostException) { - return PlaybackException.ERROR_CODE_IO_DNS_FAILED; - } - @Nullable String message = exception.getMessage(); - if (message != null) { - if (message.contains("net::ERR_INTERNET_DISCONNECTED")) { - return PlaybackException.ERROR_CODE_IO_NETWORK_UNAVAILABLE; - } else if (message.contains("net::ERR_CONTENT_LENGTH_MISMATCH") - || message.contains("net::ERR_SOCKET_NOT_CONNECTED")) { - return PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_CLOSED; - } - } - if (occurredWhileOpeningConnection) { - return PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED; - } - return PlaybackException.ERROR_CODE_IO_UNSPECIFIED; - } - private static boolean isCompressed(UrlResponseInfo info) { for (Map.Entry entry : info.getAllHeadersAsList()) { if (entry.getKey().equalsIgnoreCase("Content-Encoding")) { diff --git a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java index 6ac144bfa2..335dcf2612 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/PlaybackException.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2; +import android.net.ConnectivityManager; import android.os.Bundle; import android.os.RemoteException; import android.os.SystemClock; @@ -48,13 +49,10 @@ public class PlaybackException extends Exception implements Bundleable { ERROR_CODE_TIMEOUT, ERROR_CODE_FAILED_RUNTIME_CHECK, ERROR_CODE_IO_UNSPECIFIED, - ERROR_CODE_IO_NETWORK_UNAVAILABLE, ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT, - ERROR_CODE_IO_NETWORK_CONNECTION_CLOSED, ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE, ERROR_CODE_IO_BAD_HTTP_STATUS, - ERROR_CODE_IO_DNS_FAILED, ERROR_CODE_IO_FILE_NOT_FOUND, ERROR_CODE_IO_NO_PERMISSION, ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED, @@ -107,32 +105,38 @@ public class PlaybackException extends Exception implements Bundleable { /** Caused by an Input/Output error which could not be identified. */ public static final int ERROR_CODE_IO_UNSPECIFIED = 2000; - /** Caused by the lack of network connectivity while trying to access a network resource. */ - public static final int ERROR_CODE_IO_NETWORK_UNAVAILABLE = 2001; - /** Caused by a failure while establishing a network connection. */ - public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 2002; + /** + * Caused by a network connection failure. + * + *

The following is a non-exhaustive list of possible reasons: + * + *

+ */ + public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 2001; /** Caused by a network timeout, meaning the server is taking too long to fulfill a request. */ - public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 2003; - /** Caused by an existing connection being unexpectedly closed. */ - public static final int ERROR_CODE_IO_NETWORK_CONNECTION_CLOSED = 2004; + public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 2002; /** * Caused by a server returning a resource with an invalid "Content-Type" HTTP header value. * *

For example, this can happen when the player is expecting a piece of media, but the server * returns a paywall HTML page, with content type "text/html". */ - public static final int ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE = 2005; + public static final int ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE = 2003; /** Caused by an HTTP server returning an unexpected HTTP response status code. */ - public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 2006; - /** Caused by the player failing to resolve a hostname. */ - public static final int ERROR_CODE_IO_DNS_FAILED = 2007; + public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 2004; /** Caused by a non-existent file. */ - public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 2008; + public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 2005; /** * Caused by lack of permission to perform an IO operation. For example, lack of permission to * access internet or external storage. */ - public static final int ERROR_CODE_IO_NO_PERMISSION = 2009; + public static final int ERROR_CODE_IO_NO_PERMISSION = 2006; /** * Caused by the player trying to access cleartext HTTP traffic (meaning http:// rather than * https://) when the app's Network Security Configuration does not permit it. @@ -140,9 +144,9 @@ public class PlaybackException extends Exception implements Bundleable { *

See this corresponding * troubleshooting topic. */ - public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 2010; + public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 2007; /** Caused by reading data out of the data bound. */ - public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 2011; + public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 2008; // Content parsing errors (3xxx). @@ -234,20 +238,14 @@ public class PlaybackException extends Exception implements Bundleable { return "ERROR_CODE_FAILED_RUNTIME_CHECK"; case ERROR_CODE_IO_UNSPECIFIED: return "ERROR_CODE_IO_UNSPECIFIED"; - case ERROR_CODE_IO_NETWORK_UNAVAILABLE: - return "ERROR_CODE_IO_NETWORK_UNAVAILABLE"; case ERROR_CODE_IO_NETWORK_CONNECTION_FAILED: return "ERROR_CODE_IO_NETWORK_CONNECTION_FAILED"; case ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT: return "ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT"; - case ERROR_CODE_IO_NETWORK_CONNECTION_CLOSED: - return "ERROR_CODE_IO_NETWORK_CONNECTION_CLOSED"; case ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE: return "ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE"; case ERROR_CODE_IO_BAD_HTTP_STATUS: return "ERROR_CODE_IO_BAD_HTTP_STATUS"; - case ERROR_CODE_IO_DNS_FAILED: - return "ERROR_CODE_IO_DNS_FAILED"; case ERROR_CODE_IO_FILE_NOT_FOUND: return "ERROR_CODE_IO_FILE_NOT_FOUND"; case ERROR_CODE_IO_NO_PERMISSION: diff --git a/library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java b/library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java index 5d590c3308..150d983348 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/upstream/HttpDataSource.java @@ -23,11 +23,11 @@ import com.google.android.exoplayer2.util.Util; import com.google.common.base.Ascii; import com.google.common.base.Predicate; import java.io.IOException; +import java.io.InterruptedIOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.SocketTimeoutException; -import java.net.UnknownHostException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -215,24 +215,20 @@ public interface HttpDataSource extends DataSource { */ public static HttpDataSourceException createForIOException( IOException cause, DataSpec dataSpec, @Type int type) { - @PlaybackException.ErrorCode int errorCode = PlaybackException.ERROR_CODE_IO_UNSPECIFIED; + @PlaybackException.ErrorCode int errorCode; + @Nullable String message = cause.getMessage(); if (cause instanceof SocketTimeoutException) { errorCode = PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT; - } else if (cause instanceof UnknownHostException) { - errorCode = PlaybackException.ERROR_CODE_IO_DNS_FAILED; + } else if (cause instanceof InterruptedIOException) { + // An interruption means the operation is being cancelled, in which case this exception + // should not cause the player to fail. If it does, it likely means that the owner of the + // operation is failing to swallow the interruption, which makes us enter an invalid state. + errorCode = PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK; + } else if (message != null + && Ascii.toLowerCase(message).matches("cleartext.*not permitted.*")) { + errorCode = PlaybackException.ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED; } else { - @Nullable String message = cause.getMessage(); - if (message != null) { - if (Ascii.toLowerCase(message).matches("cleartext.*not permitted.*")) { - errorCode = PlaybackException.ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED; - } else if (message.contains("unexpected end of stream")) { - errorCode = PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_CLOSED; - } - } - - if (type == TYPE_OPEN && errorCode == PlaybackException.ERROR_CODE_IO_UNSPECIFIED) { - errorCode = PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED; - } + errorCode = PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED; } return errorCode == PlaybackException.ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED ? new CleartextNotPermittedException(cause, dataSpec) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java index 3af0cb9b24..df0ff1f1cc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/UdpDataSource.java @@ -22,16 +22,12 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.PlaybackException; import java.io.IOException; -import java.net.BindException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.MulticastSocket; -import java.net.PortUnreachableException; -import java.net.SocketException; import java.net.SocketTimeoutException; -import java.net.UnknownHostException; /** A UDP {@link DataSource}. */ public final class UdpDataSource extends BaseDataSource { @@ -115,25 +111,14 @@ public final class UdpDataSource extends BaseDataSource { } else { socket = new DatagramSocket(socketAddress); } + socket.setSoTimeout(socketTimeoutMillis); } catch (SecurityException e) { throw new UdpDataSourceException(e, PlaybackException.ERROR_CODE_IO_NO_PERMISSION); - } catch (UnknownHostException e) { - // TODO (internal b/184262323): Handle the case where UnknownHostException is thrown due to - // lack of network access. - throw new UdpDataSourceException(e, PlaybackException.ERROR_CODE_IO_DNS_FAILED); - } catch (BindException e) { - throw new UdpDataSourceException(e, PlaybackException.ERROR_CODE_IO_NETWORK_UNAVAILABLE); } catch (IOException e) { throw new UdpDataSourceException( e, PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED); } - try { - socket.setSoTimeout(socketTimeoutMillis); - } catch (SocketException e) { - throw new UdpDataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); - } - opened = true; transferStarted(dataSpec); return C.LENGTH_UNSET; @@ -149,13 +134,12 @@ public final class UdpDataSource extends BaseDataSource { // We've read all of the data from the current packet. Get another. try { socket.receive(packet); - } catch (PortUnreachableException e) { - throw new UdpDataSourceException(e, PlaybackException.ERROR_CODE_IO_NETWORK_UNAVAILABLE); } catch (SocketTimeoutException e) { throw new UdpDataSourceException( e, PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT); } catch (IOException e) { - throw new UdpDataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED); + throw new UdpDataSourceException( + e, PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED); } packetRemaining = packet.getLength(); bytesTransferred(packetRemaining);