WiFi Audio Streaming Android v1.1 🎉
I said 1.0 would be the release where I finally let this thing rest for a while.
That lasted about as long as it took the desktop app to get the same idea.
So here we are again: new protocol, new security model, new automation system, and a UI refresh nobody asked for but that I did anyway.
⚠️ Before You Update
This one's important, so no burying it at the bottom this time.
This release speaks WFAS Protocol v2. It is not wire-compatible with v1.0, and it's not compatible with old versions of the desktop app either.
Update both sides together. Same as always, you know the drill by now.
The good news: incompatibility is no longer a mystery.
Old and new versions used to just sit there in silence, pretending to connect while doing absolutely nothing. Now the handshake itself checks the protocol version on both ends, and if they don't match, you get an immediate, clear message telling you exactly which app needs updating.
No more staring at "Waiting..." wondering if it's your Wi-Fi, your firewall, or personal betrayal.
🎨 A Real Material 3 Expressive Redesign
Let's be honest about where the UI was coming from: it worked, but it had that unmistakable "solo dev, 2am, it compiles" look.
Functional, a little sad, definitely not something you'd screenshot on purpose.
This release throws that out and does it properly.
The whole app has been rebuilt on Material 3 Expressive, and I mean actually rebuilt, following the guidelines end to end instead of bolting a couple of trendy components onto the old bones like last time.
Motion, shape, color, spacing... all of it redone with intent.
The home screen got reorganized too, since there's a lot more to show now (security, automation, all of it) and the old layout simply didn't have room.
It's not going to win any design awards, but it's genuinely a different app to look at, and honestly, to use.
🌑 Dark Screen Mode
This one's a bit more specific than it sounds, but it fixes something genuinely annoying.
When your phone is acting as a client, locking the screen or leaving the app makes Android start throttling it in the background to save battery, which introduces audio artifacts.
Not ideal when you're actively listening to a stream.
So for OLED screens specifically, there's now a Dark Screen button.
It keeps the app open and the screen technically on (so Android won't throttle it), but displays either:
- a completely black overlay, or
- a sleek outlined theme,
your choice under Settings → Personalization.
Best of both worlds: the screen looks off, the battery mostly agrees, and the stream stays glitch-free.
🌐 WFAS Protocol v2
The biggest change in this release is the one you'll never actually see: the protocol itself.
Until now, the wire format only really existed inside this app's source code.
That was fine right up until people started asking how to build their own compatible clients, and the honest answer was:
"Read the Kotlin."
Not a great answer.
So WFAS now has its own home, separate from both apps:
👉 https://github.com/marcomorosi06/wfas-protocol
It's not just a written specification.
It also includes a tiny dependency-free C99 reference implementation (wfas.c + wfas.h) aimed at embedded and firmware projects:
- ESP32 network speakers
- STM32 receivers
- RP2040 devices
- Raspberry Pi appliances
- ...or whatever people end up building.
Features include:
- No dynamic memory allocation
- No libc beyond the basics
- Hand-written endianness-safe serialization
- Identical behavior on big- and little-endian MCUs
Just give it a UDP socket and it gives you correctly framed packets.
Under the hood
An audio packet remains refreshingly simple:
- 10-byte header containing:
- magic bytes
- protocol version
- flags (including silence frames)
- sequence number
- monotonic sample position
- followed directly by raw 16-bit PCM.
That sample position allows receivers to detect packet loss or reordering and conceal gaps cleanly instead of simply guessing.
Discovery, handshake, and keep-alives all use the same UDP socket through plain ASCII control messages:
HELLO_FROM_CLIENTPINGBYEWFAS_INCOMPATIBLEWFAS_BUSY
Nothing exotic to implement, and everything is documented down to the byte level.
Browser Developer Lab
If you'd rather explore the protocol without writing code, there's also an in-browser lab (still under construction) that already includes:
- packet inspector
- live handshake walkthrough
- HMAC-SHA256 demo
- HKDF-SHA256 demo
- from-scratch ChaCha20-Poly1305 implementation
- RFC 8439 test-vector validation
- self-test panel
Everything runs entirely client-side.
👉 https://www.marcomorosi.eu/wifi-audio-streaming/developers/
Android and Desktop are now simply two implementations of WFAS, not the definition of it.
🔒 Password Protection & End-to-End Encryption
Probably the single most requested feature.
Finally here.
When starting a server, you can now choose who is allowed to connect:
- Off — anyone can connect, just like before.
- Ask — approve every incoming connection manually.
- Key — only clients that know a shared password may connect.
Key Mode
Key mode provides two layers of security.
Authentication
During the handshake, both devices prove they know the shared password using a mutual challenge-response.
The password itself is never transmitted over the network.
Encryption
Once authentication succeeds, that same secret derives unique session keys.
Every audio packet is then:
- encrypted with ChaCha20-Poly1305
- authenticated before being accepted
Anyone sniffing your Wi-Fi traffic will only see encrypted noise.
The literal kind.
This is especially useful on shared networks like dorms, offices, hotels, or anywhere you don't fully trust everyone else on the SSID.
Development story
Testing this feature was... memorable.
At one point I had a bug on the decryption side, so instead of clean audio I got raw encrypted garbage blasted directly into my ears at full volume.
Imagine the soundtrack to an exorcism.
I nearly launched myself out of my chair.
Anyway, it's fixed now, and I promise the feature is significantly calmer to use than it was to build.
To be clear, that bug only ever existed during development.
If decryption fails for any reason (wrong password, corrupted packet, etc.), the app simply drops the packet.
Worst case:
- you can't connect, or
- you hear a brief moment of silence.
Never surprise horror-movie sound effects (as far as I know).
🧠 Smarter Device Matching
Device discovery is now more reliable.
You're less likely to see:
- duplicate devices
- stale entries
- servers disappearing permanently after a brief network interruption
⚙️ Automation & Shortcuts
The app can now be controlled externally through:
- NFC tags
- Tasker
- MacroDroid
- home-screen shortcuts
- deep links
- or whatever automation setup you prefer.
You can:
- start a server with a predefined configuration
- connect to a saved device
- mute the microphone
- stop streaming
...all without opening the app.
You can also save complete server or client configurations as named presets from Settings, making an entire setup just:
- one tap,
- one NFC scan,
- or one shortcut away.
🔄 Update Checker
The app can now check GitHub for new releases automatically and notify you when one is available.
Or just tap Check for Updates whenever you feel like it.
Prefer not to know?
You can disable it entirely.
🔧 Other Changes
- Haptic feedback on key interactions (optional).
- Advanced audio settings now expose latency and maximum payload size.
- General connection-state improvements.
- Rejected or pending connections no longer incorrectly show the UI as "streaming" before the handshake actually succeeds.
💻 Update the Desktop App Too
WiFi Audio Streaming Desktop v1.1 is available too.
Besides the protocol update, it includes:
- automatic updates
- firewall assistant
- full CLI
- and apparently a working ESP32 implementation held together by a cut headphone cable.
Worth a look even if you mainly use the Android app.
👉 https://github.com/marcomorosi06/WiFiAudioStreaming-Desktop/releases
Thanks, as always, to everyone testing betas and filing issues.
The microphone fixes, the security model, the discovery improvements... none of this happens without someone finding the bug first.
If you'd like to support the project, Ko-fi is there. ☕