fix(flutter/android): permit cleartext to 127.0.0.1 for just_audio loopback proxy

Audio playback failed against the prod HTTPS server with
"Cleartext HTTP traffic to 127.0.0.1 not permitted". Diagnostic
logging confirmed _baseUrl was correctly set to the prod URL — the
127.0.0.1 wasn't coming from the server or our URL construction.

Root cause: just_audio + audio_service spin up a local HTTP proxy on
loopback to inject the Authorization header, because Android's
MediaSession API can't pass headers down to ExoPlayer directly.
ExoPlayer connects to http://127.0.0.1:<random-port>; the proxy adds
the Bearer header and forwards to the real upstream HTTPS URL.
Loopback traffic doesn't leave the device.

This is the documented just_audio happy path — see
https://pub.dev/packages/just_audio#a-note-on-android-cleartext-traffic
— not a workaround for a plugin bug.

Adds network_security_config.xml that scopes cleartext permission to
127.0.0.1 ONLY. The base config keeps cleartextTrafficPermitted=false
so any actual remote endpoint must still be HTTPS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 23:24:47 -04:00
parent 83df3773ae
commit 28b07c7ef4
2 changed files with 23 additions and 1 deletions
@@ -12,7 +12,8 @@
<application
android:label="minstrel"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".MainActivity"
android:exported="true"
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Permits cleartext HTTP to 127.0.0.1 ONLY. Required because just_audio +
audio_service spin up a local HTTP proxy on loopback to inject the
Authorization header (Android's MediaSession API can't pass headers down
to ExoPlayer directly). Without this exemption, ExoPlayer hits Android's
cleartext block when connecting to the local proxy and audio playback
fails with "Cleartext HTTP traffic to 127.0.0.1 not permitted".
All other hosts inherit cleartextTrafficPermitted="false" — actual
remote traffic (to your Minstrel server) must still be HTTPS.
Loopback traffic doesn't leave the device, so this exemption doesn't
reduce security for real network endpoints.
-->
<network-security-config>
<base-config cleartextTrafficPermitted="false" />
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">127.0.0.1</domain>
</domain-config>
</network-security-config>