Sono Bar Case Study

Building a Sonos controller
that lives in 4 MB.

Sono Bar is a macOS menu bar app that controls Sonos speakers over the local network using reverse-engineered UPnP/SOAP. No cloud, no account, no Electron. This is the story of how and why we built it that way.

The problem

Sonos speakers are great hardware with frustrating software. Want to pause music? Open the full Sonos app, wait for it to discover your network, find the right room, then tap pause. For something you do fifty times a day, that's a lot of friction.

What we wanted was dead simple: a menu bar icon that toggles play/pause on click, adjusts volume on scroll, and gets out of the way. No window. No dock icon. No account screen.

The catch: Sonos doesn't offer a local API for third-party apps. Their cloud API requires OAuth and internet access — overkill for controlling a speaker on your own WiFi. The only local interface is an undocumented UPnP/SOAP protocol on port 1400.

~4 MB
Binary Size
vs ~150 MB for a comparable Electron app
~15 MB
RAM at Idle
stays flat — no garbage collector, no V8 heap growth
Continuous
Poll Interval
transport state checked on a tight loop, topology every 30s
150 ms
Volume Debounce
UI updates instantly, SOAP command batches to final value
< 1 s
Startup
native binary, no runtime to bootstrap
Zero runtime
Dependencies
no Node.js, no Python, no JVM — just the compiled binary

Challenges worth solving.

Every interesting project has a handful of problems that make you rethink your assumptions. Here are ours.

No Official Local API

Problem Sonos has a cloud API, but no documented way to control speakers over the local network. The only option is UPnP/SOAP on port 1400 — an undocumented protocol that Sonos doesn't officially support for third-party apps.

Solution Reverse-engineered the SOAP endpoints by inspecting network traffic between the official Sonos app and the speakers. Built a Rust client that speaks UPnP discovery (SSDP) and SOAP control directly, with no external dependencies or API keys.

Real-Time State Without Websockets

Problem Sonos speakers don't push state changes. If someone pauses music from their phone, the menu bar icon needs to reflect that — but there's no event stream to subscribe to.

Solution Implemented a lightweight polling loop that checks transport state and volume continuously, with topology refresh every 30 seconds to detect group changes. The icon updates instantly when a speaker goes offline or changes state.

Volume Feels Laggy Over SOAP

Problem Each volume change requires a full SOAP HTTP request. When scrolling the mouse wheel quickly, sending a request per scroll event floods the network and makes the UI feel sluggish.

Solution Added a 150ms debounce on SOAP commands while keeping the UI instantly responsive. The displayed volume updates on every scroll tick, but the actual network command batches to the final value. Result: the UI feels native, and the speaker catches up smoothly.

Native Menu Bar, Not a Web Window

Problem Most Tauri apps use a web view for UI. But menu bar apps need to feel like part of macOS — real NSMenu items, native keyboard shortcuts, proper dark mode support. A web-rendered dropdown would look and feel wrong.

Solution Used Tauri 2's system tray API with native NSMenu and NSStatusItem. The right-click menu is built entirely in Rust, rendered by macOS, and indistinguishable from any Apple-built menu bar app. No HTML, no CSS, no web view for the menu itself.

Speaker Groups Change Dynamically

Problem Sonos speakers can be grouped and ungrouped at any time from any controller. A speaker that was standalone five minutes ago might now be part of a stereo pair or a multi-room group, changing which device to send commands to.

Solution Built a topology tracker that periodically refreshes the household zone group state. When groups change, the active target updates automatically. Volume commands adjust the group coordinator, so all speakers in a group move together.

Why we chose what we chose.

Every technical decision is a tradeoff. Here's what we optimized for and what we gave up.

Rust + Tauri 2 over Electron

A menu bar utility should be invisible — tiny memory footprint, instant launch, no battery drain. Electron would mean ~150 MB on disk and ~80 MB RAM at idle. Tauri + Rust gives us a ~4 MB binary using ~15 MB RAM.

UPnP/SOAP over Sonos Cloud API

The cloud API requires OAuth, an internet connection, and Sonos account credentials. A local-only approach means zero setup, zero latency from cloud round-trips, and it works even when Sonos servers are down.

Deliberate Feature Ceiling

No EQ, no room tuning, no music search, no speaker grouping. The official app does all of that. Sono Bar does exactly one thing: quick playback control from the menu bar. Every feature request is measured against this constraint.

Apple Silicon Only

Dropping Intel support eliminated a Rosetta compatibility layer, simplified the build pipeline, and let us target a single architecture. The tradeoff is a smaller audience, but the binary is leaner and the CI is simpler.

Stack

Framework Tauri 2
Language Rust
Protocol UPnP / SOAP
UI Native NSMenu + NSStatusItem
Platform macOS 12+ (Apple Silicon)
Discovery SSDP (Simple Service Discovery Protocol)

Want to see it in action?

Visit the product page to download Sono Bar or see the interactive demos.