Heads-up for Shairport-Sync

Building a Web Interface for Shairport-Sync

I’ve been running shairport-sync on various Raspberry Pis and other devices around my house for years. For those unfamiliar, shairport-sync transforms any Linux machine into an AirPlay audio receiver, letting you stream music from your iPhone, Mac, or any other Apple device to your stereo system, active speakers, or whatever audio setup you’ve cobbled together. And, I’ve definitely cobbled.

It works brilliantly, until you realise you have absolutely no idea what’s currently playing on any of your AirPlay receivers unless you’re standing in front of the device that’s streaming.

The Problem: Invisible Music

Here’s the scenario: You’ve got music playing somewhere in the house. Maybe it’s coming from the kitchen Pi, or perhaps the office setup. You want to skip to the next track, or see what album is playing, or just check who the artist is. Your options are:

  1. Walk to wherever your phone or iPad is
  2. Pick it up
  3. Hope it’s still on the right AirPlay output
  4. Check the now-playing info

The Solution: MQTT to the Rescue

Shairport-sync has a fantastic feature that many people don’t know about: it can publish metadata about what’s currently playing via MQTT. Album art, track info, artist details, even playback progress—it’s all there, ready to be enabled to stream over your local network in near real-time.

The problem? There wasn’t a simple, clean web interface to consume this information and provide basic transport controls.

So I built one.

Introducing shairport-mqtt-web

shairport-mqtt-web is a lightweight Flask web application that sits between your MQTT broker and your browser, giving you a clean, responsive interface to see what’s playing and control playback.

What It Does

The app provides:

  • Real-time now-playing information: Artist, album, track title, and genre
  • Album artwork display: Pulled directly from the stream
  • Transport controls: Play/pause, next, previous, volume
  • Playback progress: Track position with time remaining
  • Server-Sent Events: Live updates without polling

How It Works

The architecture is straightforward:

  1. Shairport-sync publishes metadata to your MQTT broker
  2. The Flask app subscribes to these MQTT topics and maintains current state
  3. A web interface displays this information and sends control commands back via MQTT
  4. Server-Sent Events push updates to the browser in real-time

The state management is dead simple—just a Python dictionary holding the current playback information. When MQTT messages arrive, the state updates, and connected browsers get notified via SSE.

Why I Built It

As a self-described “maximizer” and maker, I get energized by designing and building solutions to real problems. Having music playable throughout the house is great, but not being able to see what’s playing at a glance felt like an itch that needed scratching. Not least becase someontimes I am not in the room and someone else wants to know what’s playing

More practically:

  • I wanted something deployable: A systemd service that just works
  • I wanted it lightweight: No heavy frameworks, no complex build processes
  • I wanted it maintainable: Poetry for dependency management, clear structure
  • I wanted it flexible: YAML configuration, easy to adapt to different setups

Technical Decisions

Why Flask?

Flask is boring technology, and that’s a compliment. It’s well-understood, has minimal overhead, and does exactly what I need without the ceremony of larger frameworks. For a simple web interface that primarily pushes state updates, it’s perfect.

Why Server-Sent Events?

WebSockets felt like overkill for one-way real-time updates. SSE is simpler, uses standard HTTP, and works brilliantly for pushing state changes from server to client. Plus, it’s just a text stream—easy to debug, easy to understand.

Why Poetry?

I’ve standardised on Poetry for all my Python projects. It handles dependency management, virtual environments, and packaging with minimal fuss. The Makefile wraps common operations making it trivial to run locally or deploy to production.

Deployment

The tool is designed to run as a systemd service on Linux systems. The deployment process is automated:

make deploy

This:

  1. Copies files to /opt/shairport-mqtt-web
  2. Creates a Python virtual environment
  3. Installs dependencies
  4. Sets up the systemd service
  5. Enables it to start on boot

It uses systemd’s DynamicUser feature, so no manual user creation is needed—the system handles security automatically.

What’s Next?

The tool does what I need it to do, which is the best kind of “done.” But there are a few nice-to-haves on my radar:

  • Better mobile responsive design
  • Support for multiple shairport-sync instances
  • Playlist/queue visibility if shairport-sync exposes that data
  • Historical playback tracking
  • The random and repeat buttons don’t seem to do anything at this point – can’t quite work out if it is not supported or just not supported on my device. But I left them in just in case.

For now, though, it scratches the itch. I can pull up a browser tab (or pin it to my desktop), see what’s playing on any of my AirPlay receivers, and control playback without hunting for my phone.

Try It Yourself

If you’re running shairport-sync and have an MQTT broker handy, give it a spin:

git clone https://github.com/timhodson/shairport-mqtt-web.git
cd shairport-mqtt-web
make install
cp config.yaml.example config.yaml
# Edit config.yaml with your MQTT details
make prod

Then visit http://localhost:5001 and you should see your currently playing track (if something’s streaming).

The code is MIT licensed and available on GitHub. Contributions, issues, and feedback welcome.

Sometimes the best projects are the ones that solve your own specific problems. This one took a weekend to build and has been running reliably ever since. That’s the beauty of simple, focused tools built on boring technology.

Now if you’ll excuse me, I need to skip to the next track. Let me just open a browser tab…

Leave a comment