scriptosandClaude Opus 5.5 67a687f4e6
Build & Push Docker Image / Build & push image (push) Successful in 6m49s
Release 0.7.1: gapless conversion only without quality loss
LOSSLESS_ONLY (default true): follow-up tracks are only converted when
nothing is lost (MP3 into a FLAC stream). FLAC after MP3 or higher
resolution FLAC plays in original quality with a normal track change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 13:18:08 +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 and OpenHome renderer 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)
  • 📜 OpenHome renderer (Product, Playlist, Time, Info, Volume): your app hands over the whole queue and the bridge plays it on its own – the phone may sleep or leave the house
  • 🔐 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; mixed formats are converted with ffmpeg
  • 🖼️ Title and cover art in the BluOS app – updated per track, also in gapless mode
  • 🌗 Soft fades on pause, stop and track changes
  • 📈 Monitoring endpoints for Uptime Kuma, PRTG and Prometheus
  • 🔁 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 (English / German): live status page (now playing, player state, event log) and a settings page behind basic auth
  • 🐳 Python standard library only, multi-arch Docker image (amd64, arm64)

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          # pulls git.techniverse.net/scriptos/bluos-upnp-bridge:latest
docker compose logs -f

Prefer building yourself? Replace image: with build: . in docker-compose.yaml and run docker compose up -d --build.

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
LOSSLESS_ONLY true Gapless: convert follow-up tracks only if no quality is lost
FADE_MS 500 Fade in/out on pause, stop and track change in milliseconds, 0 = off
OPENHOME true Offer the OpenHome services (restart required)
UI_LANG en Language of the web UI: en or de
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
/api/health – Health JSON – HTTP 200 if bridge, player and music server are fine, otherwise 503
/api/prtg – PRTG "HTTP Data Advanced" sensor format (bridge, player, server, latency, playing, uptime)
/metrics – Prometheus metrics

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

Uptime Kuma: monitor type HTTP(s) on http://<bridge-ip>:49494/api/health (accepted status code 200). PRTG: sensor HTTP Data Advanced with URL http://<bridge-ip>:49494/api/prtg.

OpenHome

Apps with OpenHome support (e.g. Symfonium, BubbleUPnP, Kazoo) can send the complete play queue to the bridge instead of one track at a time. The bridge then plays the queue by itself, keeps the BluOS player fed with the current and the following track (gapless mode included) and reports position, track and volume back to every connected app. Your phone can go to sleep – the music keeps playing.

Supported services: Product (single "Playlist" source), Playlist (incl. repeat), Time, Info, Volume. Shuffle is reported but left to the app (it sends the queue in shuffled order).

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 your app announces (SetNextAVTransportURI) about 20 seconds before it is due – queue changes in the app until then are respected:

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 and its cover (ICY metadata). Seeking restarts the stream at the new position (short pause). The stream takes the format of its first track. Follow-up tracks in another format are converted with ffmpeg – by default only if no quality is lost (LOSSLESS_ONLY=true):

Stream Follow-up track Result
FLAC MP3 converted to FLAC (just re-packed, no further loss) – gapless
FLAC FLAC with higher sample rate / bit depth not converted – normal track change, original quality
MP3 FLAC not converted – normal track change, FLAC in original quality

With LOSSLESS_ONLY=false every follow-up track is converted (FLAC → MP3 with 320 kbit/s, resampling). Albums ripped in one format are never converted.

BluOS drops paused "radio" streams after about two minutes and reconnects on resume – the bridge then continues exactly where playback was paused.

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.
  • Image size: ffmpeg (for gapless format conversion) makes the image ~550 MB.
  • 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
Airsonic Pulse 13.2.1

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%