scriptosandClaude Opus 5.5 e813e3ed56 Release 0.5.1: OpenSubsonic servers (Airsonic Pulse), client name
- SERVER_TYPE subsonic/opensubsonic: API key auth, password auth as fallback
  for servers that store hashed passwords (Airsonic Pulse)
- SUBSONIC_ALIASES for public domain vs. internal server address
- Covers for the player served by the bridge (no credentials on the player)
- CLIENT_NAME: configurable client name shown by the music server

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:13:52 +01:00
2026-10-24 16:41:00 +02:00

Techniverse Community

bluos-upnp-bridge

Play your Subsonic / DLNA music on Bluesound (BluOS) players – controlled from Symfonium or any other UPnP app

🏰 Website · 📰 Community · 🐘 Mastodon · 💬 Support


Why?

BluOS players (Bluesound, NAD, …) can't be extended with your own music services, and BluOS deliberately does not act as a UPnP/DLNA renderer. If your music lives in Subsonic, Airsonic, Navidrome, gonic or any other DLNA-capable library, the only "native" ways are SMB shares (no playlists, no favourites) or AirPlay (16 bit, phone has to stay in charge).

bluos-upnp-bridge makes a BluOS player show up as a regular UPnP/DLNA MediaRenderer on your network. Pick it in Symfonium (or BubbleUPnP, Home Assistant, …), browse your own library with your playlists and favourites – and the Bluesound plays the original file directly from your server. No transcoding, no AirPlay, no extra app.

 Symfonium (phone)  ── UPnP: "play URL X" ──▶  bluos-upnp-bridge  ── BluOS API :11000 ──▶  Bluesound player
                                                     │                                        │
                                                     └── rewrites Subsonic credentials        └── fetches the stream
                                                         to a stream-only service account        directly from Subsonic

Features

  • 🎵 UPnP/DLNA MediaRenderer (AVTransport, RenderingControl, ConnectionManager, GENA events, SSDP discovery)
  • 🔐 Credential rewriting for Subsonic: the URLs your app hands over contain your login. The bridge replaces them with a dedicated, stream-only service account (token auth) – your password never reaches the player. URLs with credentials that can't be replaced are refused instead of being passed on.
  • 🎚️ Play, pause, stop, next track (queued via SetNextAVTransportURI, short gap between tracks), volume and mute
  • ⏩ Seeking through a built-in stream proxy (Content-Length + byte ranges)
  • 🔗 Gapless playback (experimental) for MP3 and FLAC – live albums, DJ mixes and audio plays run seamlessly from track to track
  • 🖼️ Title and cover art in the BluOS app
  • 🔁 Handles BluOS quirks: falls back to its own queue at the end of a stream, "repeat one" loops URLs, download.view is refused by BluOS (rewritten to stream.view)
  • 📊 Web UI: live status page (now playing, player state, event log) and a settings page behind basic auth
  • 🐳 Single Python file, standard library only, tiny Docker image

Requirements

  • A BluOS player reachable on TCP 11000 from the bridge
  • Docker host in the same network as your control point, or an SSDP relay (UDP 1900, 239.255.255.250) between the VLANs – see Networking
  • For Subsonic: a local (non-LDAP) user that is only allowed to stream (see below)

Quick start

git clone https://git.techniverse.net/scriptos/bluos-upnp-bridge.git
cd bluos-upnp-bridge
cp .env.example .env
nano .env                     # player IP, bridge IP, Subsonic service account, web password
mkdir -p data && sudo chown 10001 data
docker compose up -d --build
docker compose logs -f

Open http://<bridge-ip>:49494/ for the status page. Your player now appears in Symfonium as " (BluOS Bridge)".

Subsonic service account

Create a local user in your Subsonic server (e.g. bluos_stream) with only the stream and cover art roles. Why a separate account?

  • The stream URL is stored on the player and visible in the BluOS app – it must not contain your own password.
  • Subsonic only supports token authentication for local users. LDAP users would need the password in the URL.

Airsonic Pulse, Navidrome & other OpenSubsonic servers

Set SERVER_TYPE=opensubsonic. Preferably create an API key for the service account (Airsonic Pulse: Personal settings → API keys) and put it into SUBSONIC_API_KEY. Without a key the bridge uses password authentication – Airsonic Pulse stores passwords hashed, so token auth isn't possible there. The password only travels between bridge and server: streams and covers reach the player through the bridge, so PROXY_STREAMS must stay enabled in this mode.

If your app talks to the server via a public domain while the bridge uses the internal address, add the domain to SUBSONIC_ALIASES so the bridge recognises the app's URLs.

Configuration

All settings come from environment variables (.env). Settings saved in the web UI are stored in data/settings.json and take precedence.

Variable Default Description
BLUOS_HOST – IP or hostname of the BluOS player (required)
BLUOS_PORT 11000 BluOS API port
ADVERTISE_IP – IP of the Docker host, announced via SSDP (required)
HTTP_PORT 49494 Port for UPnP and the web UI
FRIENDLY_NAME player name + (BluOS Bridge) Name shown in your UPnP app
SERVER_TYPE subsonic subsonic (classic Subsonic, token auth) or opensubsonic (Airsonic Pulse, Navidrome, … – API key, without key password auth)
SUBSONIC_URL – Music server as reached from the bridge, e.g. http://192.168.1.20:4040 – enables credential rewriting
SUBSONIC_ALIASES – Comma-separated further hostnames your app uses for the same server (e.g. its public domain)
SUBSONIC_USER – Stream-only service account
SUBSONIC_PASSWORD – Its password
SUBSONIC_API_KEY – API key of the service account (opensubsonic only, preferred)
CLIENT_NAME bluos-upnp-bridge Client name shown by the server (e.g. user@BluOS Player); {player} = BluOS player name
PROXY_STREAMS true Serve Subsonic streams through the bridge (Content-Length + byte ranges → seeking, clean track end)
GAPLESS false Experimental gapless playback (needs PROXY_STREAMS), see Gapless playback
REPEAT_OFF true Switch BluOS repeat off when the bridge starts playback (your app handles repeat)
WEB_USER / WEB_PASSWORD admin / – Basic auth for /settings. Settings are disabled without a password
SSDP_INTERVAL 60 Seconds between SSDP announcements
LOG_LEVEL INFO DEBUG, INFO or WARNING
DATA_DIR /data Where settings.json is stored

Web UI

URL Auth Content
/ – Now playing, player and bridge status, live event log
/settings Basic auth Change settings, test player and Subsonic login, pause/resume, restart the bridge
/api/status, /api/log – JSON used by the status page

The status page never shows credentials or player URLs; the cover is proxied through the bridge. The UI texts are currently German. Basic auth runs over plain HTTP – put a reverse proxy with TLS in front if your network isn't trusted.

Gapless playback

BluOS restarts its audio pipeline for every URL, so normally there is a short gap between tracks. With GAPLESS=true (or the switch on the settings page) the bridge serves one continuous stream instead and appends the next track as soon as your app announces it (SetNextAVTransportURI):

Format How it's stitched
MP3 ID3v2/ID3v1 tags and the Xing/Info frame of follow-up tracks are dropped, MPEG frames are concatenated. A few milliseconds of encoder padding remain.
FLAC The first track's STREAMINFO is marked open-ended (total samples / MD5 unknown), follow-up tracks are appended without their metadata blocks. Requires identical sample rate, channels and bit depth.

The bridge maps BluOS' playback position to the audible track and reports every track change to your app. The BluOS app shows the current track in its second line (ICY metadata); the cover stays at the first track. Seeking restarts the stream at the new position (short pause). If two tracks can't be stitched (e.g. MP3 → FLAC), the bridge falls back to a normal track change.

Networking

  • Host network is required (network_mode: host): SSDP discovery needs multicast on the LAN interface.
  • VLANs: if your phone and the bridge live in different networks, relay SSDP (UDP 1900, 239.255.255.250) between them. On OPNsense/pfSense the udpbroadcastrelay plugin does the job. mDNS repeaters are not enough.
  • Firewall: bridge → player TCP 11000, control point → bridge TCP 49494, player → bridge TCP 49494 (stream proxy), bridge → your music server (HTTP/HTTPS).
  • ⚠️ Docker subnets: if a Docker network on the host overlaps the player's subnet (e.g. a default 172.20.0.0/16 bridge network and the player in 172.20.x.x), the bridge gets No route to host. Check with ip route get <player-ip>.

Known limitations

  • Track transitions: without GAPLESS there is a short gap between tracks.
  • Seeking needs the stream proxy (PROXY_STREAMS=true, default) – without it BluOS can't seek Subsonic streams.
  • Metadata: BluOS only accepts a single title line and a cover image for URL playback, so the bridge shows Title – Artist. Album is not shown in the BluOS app.
  • One player per instance: run one container per player (different HTTP_PORT). Groups created in the BluOS app work: if the controlled player is the group master, everything plays in sync.

Tested with

Component Version
Bluesound PULSE MINI 2i BluOS 4.16.6
Symfonium (Android) current Play Store version
Subsonic 6.1.6

Other UPnP control points and Subsonic-compatible servers should work as well – feedback is welcome.

Disclaimer

This project is not affiliated with, endorsed or supported by Lenbrook (Bluesound, NAD, BluOS). It uses the local BluOS HTTP API as available on the player. All trademarks belong to their respective owners.



© Patrick Asmus · Techniverse Network · License

S
Description
Play Subsonic/DLNA music on Bluesound (BluOS) players: UPnP/DLNA MediaRenderer bridge for Symfonium & co.
Readme MIT
358 KiB
Languages
Python 99.5%
Dockerfile 0.5%