diff --git a/demos/main/src/main/res/values/strings.xml b/demos/main/src/main/res/values/strings.xml index 9085c43bd3..49441ef7da 100644 --- a/demos/main/src/main/res/values/strings.xml +++ b/demos/main/src/main/res/values/strings.xml @@ -21,7 +21,7 @@ Unexpected intent action: %1$s - Cleartext traffic not permitted + Cleartext HTTP traffic not permitted. See https://exoplayer.dev/issues/cleartext-not-permitted Playback failed 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 461d18fd26..b215b6d763 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 @@ -443,8 +443,14 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { transferInitializing(dataSpec); try { boolean connectionOpened = blockUntilConnectTimeout(); - if (exception != null) { - throw new OpenException(exception, dataSpec, getStatus(urlRequest)); + @Nullable IOException connectionOpenException = exception; + if (connectionOpenException != null) { + @Nullable String message = connectionOpenException.getMessage(); + if (message != null + && Util.toLowerInvariant(message).contains("err_cleartext_not_permitted")) { + throw new CleartextNotPermittedException(connectionOpenException, dataSpec); + } + throw new OpenException(connectionOpenException, dataSpec, getStatus(urlRequest)); } else if (!connectionOpened) { // The timeout was reached before the connection was opened. throw new OpenException(new SocketTimeoutException(), dataSpec, getStatus(urlRequest)); 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 57fee20d04..85d3530e2d 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 @@ -242,6 +242,11 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { responseBody = Assertions.checkNotNull(response.body()); responseByteStream = responseBody.byteStream(); } catch (IOException e) { + @Nullable String message = e.getMessage(); + if (message != null + && Util.toLowerInvariant(message).matches("cleartext communication.*not permitted.*")) { + throw new CleartextNotPermittedException(e, dataSpec); + } throw new HttpDataSourceException( "Unable to connect", e, dataSpec, HttpDataSourceException.TYPE_OPEN); } 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 d2170b9eab..1c450ca02d 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 @@ -271,7 +271,24 @@ public interface HttpDataSource extends DataSource { this.dataSpec = dataSpec; this.type = type; } + } + /** + * Thrown when cleartext HTTP traffic is not permitted. For more information including how to + * enable cleartext traffic, see the corresponding troubleshooting + * topic. + */ + final class CleartextNotPermittedException extends HttpDataSourceException { + + public CleartextNotPermittedException(IOException cause, DataSpec dataSpec) { + super( + "Cleartext HTTP traffic not permitted. See" + + " https://exoplayer.dev/issues/cleartext-not-permitted", + cause, + dataSpec, + TYPE_OPEN); + } } /** @@ -285,7 +302,6 @@ public interface HttpDataSource extends DataSource { super("Invalid content type: " + contentType, dataSpec, TYPE_OPEN); this.contentType = contentType; } - } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index d15804fd51..94c02c7e83 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -306,6 +306,11 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou try { connection = makeConnection(dataSpec); } catch (IOException e) { + @Nullable String message = e.getMessage(); + if (message != null + && Util.toLowerInvariant(message).matches("cleartext http traffic.*not permitted.*")) { + throw new CleartextNotPermittedException(e, dataSpec); + } throw new HttpDataSourceException( "Unable to connect", e, dataSpec, HttpDataSourceException.TYPE_OPEN); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java index dc5aefac6d..091c57de10 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultLoadErrorHandlingPolicy.java @@ -19,6 +19,7 @@ import static java.lang.Math.min; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.upstream.HttpDataSource.CleartextNotPermittedException; import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; import com.google.android.exoplayer2.upstream.Loader.UnexpectedLoaderException; import java.io.FileNotFoundException; @@ -86,14 +87,16 @@ public class DefaultLoadErrorHandlingPolicy implements LoadErrorHandlingPolicy { /** * Retries for any exception that is not a subclass of {@link ParserException}, {@link - * FileNotFoundException} or {@link UnexpectedLoaderException}. The retry delay is calculated as - * {@code Math.min((errorCount - 1) * 1000, 5000)}. + * FileNotFoundException}, {@link CleartextNotPermittedException} or {@link + * UnexpectedLoaderException}. The retry delay is calculated as {@code Math.min((errorCount - 1) * + * 1000, 5000)}. */ @Override public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) { IOException exception = loadErrorInfo.exception; return exception instanceof ParserException || exception instanceof FileNotFoundException + || exception instanceof CleartextNotPermittedException || exception instanceof UnexpectedLoaderException ? C.TIME_UNSET : min((loadErrorInfo.errorCount - 1) * 1000, 5000);