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);