{"record":{"id":"04942092a7a14c72","repo":"google/ExoPlayer","slug":"error-code-io-cleartext-not-permitted","errorCode":"ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED","errorMessage":"Cleartext HTTP traffic not permitted. See https://developer.android.com/guide/topics/media/issues/cleartext-not-permitted","messagePattern":"Cleartext HTTP traffic not permitted\\. See https://developer\\.android\\.com/guide/topics/media/issues/cleartext-not-permitted","errorType":"http","errorClass":"HttpDataSource.CleartextNotPermittedException","httpStatus":null,"severity":"error","filePath":"extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java","lineNumber":557,"sourceCode":"      currentUrlRequest = urlRequest;\n    } catch (IOException e) {\n      if (e instanceof HttpDataSourceException) {\n        throw (HttpDataSourceException) e;\n      } else {\n        throw new OpenException(\n            e, dataSpec, PlaybackException.ERROR_CODE_IO_UNSPECIFIED, Status.IDLE);\n      }\n    }\n    urlRequest.start();\n\n    transferInitializing(dataSpec);\n    try {\n      boolean connectionOpened = blockUntilConnectTimeout();\n      @Nullable IOException connectionOpenException = exception;\n      if (connectionOpenException != null) {\n        @Nullable String message = connectionOpenException.getMessage();\n        if (message != null && Ascii.toLowerCase(message).contains(\"err_cleartext_not_permitted\")) {\n          throw new CleartextNotPermittedException(connectionOpenException, dataSpec);\n        }\n        throw new OpenException(\n            connectionOpenException,\n            dataSpec,\n            PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED,\n            getStatus(urlRequest));\n      } else if (!connectionOpened) {\n        // The timeout was reached before the connection was opened.\n        throw new OpenException(\n            new SocketTimeoutException(),\n            dataSpec,\n            PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT,\n            getStatus(urlRequest));\n      }\n    } catch (InterruptedException e) {\n      Thread.currentThread().interrupt();\n      // An interruption means the operation is being cancelled, in which case this exception should\n      // not cause the player to fail. If it does, it likely means that the owner of the operation","sourceCodeStart":539,"sourceCodeEnd":575,"githubUrl":"https://github.com/google/ExoPlayer/blob/dd430f7053a1a3958deea3ead6a0565150c06bfc/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetDataSource.java#L539-L575","documentation":"CleartextNotPermittedException (PlaybackException error code ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED) thrown by CronetDataSource when the connection failure message contains 'err_cleartext_not_permitted'. Since Android 9 (API 28), plain http:// requests are blocked by default unless the app opts in, so any http data source open fails with this error.","triggerScenarios":"Opening a DataSpec with an http:// (non-TLS) URL through CronetDataSource on API 28+ without a cleartext opt-in; a redirect from https:// to an http:// host; playlists whose segment URIs are absolute http:// while the manifest was https://.","commonSituations":"Test/dev servers on plain http; content from CDNs that serve http segments; app targetSdk >= 28 where android:usesCleartextTraffic defaults to false.","solutions":["Serve the media over https:// — this is the correct production fix and the one the error URL recommends","For debug builds only, set android:usesCleartextTraffic=\"true\" in the manifest, or a network security config permitting cleartext for the specific dev domain","Audit playlist/manifest segment URIs and redirects to ensure none downgrade to http","Pre-check the scheme: refuse or rewrite http:// DataSpecs before calling dataSource.open()"],"exampleFix":"<!-- before: no opt-in, http blocked on API 28+ -->\n<application ...>\n\n<!-- after (debug only): targeted network security config -->\n<!-- res/xml/network_security_config.xml -->\n<network-security-config>\n  <domain-config cleartextTrafficPermitted=\"true\">\n    <domain includeSubdomains=\"true\">dev.example.com</domain>\n  </domain-config>\n</network-security-config>\n<!-- AndroidManifest.xml -->\n<application android:networkSecurityConfig=\"@xml/network_security_config\" ...>","handlingStrategy":"fallback","validationCode":"Uri uri = Uri.parse(dataSpec.uri);\nif (\"http\".equals(uri.getScheme())\n    && Build.VERSION.SDK_INT >= 28\n    && !NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted(uri.getHost())) {\n  // rewrite to https or fail with a clear message BEFORE dataSource.open()\n  uri = uri.buildUpon().scheme(\"https\").build();\n}","typeGuard":null,"tryCatchPattern":"try {\n  dataSource.open(dataSpec);\n} catch (CleartextNotPermittedException e) {\n  // the URL is plain http and Android blocks it: switch origin to https and retry\n  DataSpec httpsSpec = dataSpec.buildUpon()\n      .setUri(dataSpec.uri.replaceFirst(\"http://\", \"https://\"))\n      .build();\n  dataSource.open(httpsSpec);\n}","preventionTips":["Serve all media over https in production; test servers get a debug-only network security config","Audit playlists and redirect chains for http:// downgrades","Check NetworkSecurityPolicy.isCleartextTrafficPermitted(host) before opening http data sources"],"tags":["android","network","cronet","http","security-config"],"backgroundTag":null,"analyzedSha":"dd430f7053a1a3958deea3ead6a0565150c06bfc","analyzedAt":"2026-08-14T12:22:02.982Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}