| Stream the impeccable vibes of Poolsuite FM — the retro-styled, SoundCloud-powered internet radio — directly to your Roon, Bluesound, or Sonos home audio system.
This bridge runs a local internet radio station that Roon picks up natively (and Bluesound / Sonos can access via TuneIn) as a Live Radio stream, complete with track metadata so your player of choice can display artist info, album art, and link to matching content in Tidal or Qobuz. This package also runs a web browser GUI to control your local radio station, which also can change what is streamed to Roon. (screenshots below) |
![]() |
| Dark Mode | Light Mode |
|---|---|
![]() |
![]() |
Poolsuite API → yt-dlp (resolve SoundCloud) → ffmpeg (transcode) → HTTP MP3 stream → Roon
- Fetches curated playlists from the Poolsuite API
- Resolves SoundCloud tracks to direct audio URLs using
yt-dlp - Transcodes to a constant-bitrate MP3 stream via
ffmpeg - Serves the stream at
http://YOUR_IP:8489/stream - Roon connects and plays it on any zone — with full DSP, grouping, and volume control
Track metadata is injected via the ICY protocol, so Roon displays the current artist and song title in real time and can cross-reference against Tidal/Qobuz for rich metadata.
| Dependency | Version | Purpose |
|---|---|---|
| Python | 3.9+ | Runtime |
| ffmpeg | Any recent | Audio transcoding (must include libmp3lame) |
| yt-dlp | Latest recommended | Resolves SoundCloud URLs to direct streams |
brew install python ffmpeg yt-dlpsudo apt update && sudo apt install -y python3 python3-pip ffmpeg
pip3 install yt-dlpsudo pacman -S python python-pip ffmpeg yt-dlpInstall WSL, then follow the Ubuntu instructions above.
# Clone the repo
git clone https://github.com/thepeterberg/Poolsuite_Roon_Streaming.git
cd poolsuite-roon-streaming
# Install Python dependencies
pip3 install -r requirements.txt
# Start the bridge
python3 main.pyYou'll see:
============================================================
Poolsuite -> Roon Bridge
============================================================
Stream URL: http://YOUR_LOCAL_IP:8489/stream
Status: http://YOUR_LOCAL_IP:8489/status
Web UI: http://YOUR_LOCAL_IP:8489/
Add the stream URL as a Live Radio station in Roon:
Roon > My Live Radio > + > paste the stream URL
============================================================
- Find your machine's local IP:
# macOS ipconfig getifaddr en0 # Linux hostname -I | awk '{print $1}'
- Open Roon on any client
- Go to My Live Radio in the sidebar
- Click + Add Station
- Paste:
http://YOUR_IP:8489/stream - Name it Poolsuite FM
- Play on any zone
Roon treats this like any internet radio station — zone grouping, volume, DSP, and signal path all work normally.
Bluesound players can play any MP3 stream URL directly via the BluOS app — no TuneIn account needed.
- Find your machine's local IP (see above)
- Open the BluOS app on your phone, tablet, or desktop
- Tap the Browse tab (bottom nav)
- Tap TuneIn (or Radio Paradise — either works, we just need to get to the custom URL option)
- Instead of browsing TuneIn, tap the three dots menu (top right) and select Add Custom Station
- Enter the stream URL:
http://YOUR_IP:8489/stream - Name it Poolsuite FM
- Tap Save
The station now appears in your My Presets or My Radio Stations list and can be played on any Bluesound player or group.
Alternative — direct play via URL: If you don't see the custom station option, you can use the BluOS API directly. Open a browser and navigate to:
http://BLUESOUND_IP:11000/Play?url=http://YOUR_IP:8489/stream&preset_id=1&image=/images/radio.png
Replace BLUESOUND_IP with your Bluesound player's IP address. This starts playback immediately.
Note: ICY metadata (track titles) works with Bluesound — you'll see the current artist and song on the player display and in the BluOS app.
Sonos can play any MP3 stream via the TuneIn "My Radio Stations" feature. This uses TuneIn's website to register your local stream URL, which then syncs to your Sonos system.
- Find your machine's local IP (see above)
- Go to tunein.com and sign in (create a free account if needed)
- Make sure your Sonos system is linked to the same TuneIn account (Sonos app > Settings > Services & Voice > TuneIn)
- On the TuneIn website, go to My Profile > My Custom URLs (or visit
https://tunein.com/user/custom-url/) - Click Add Custom URL
- Enter the stream URL:
http://YOUR_IP:8489/stream - Name it Poolsuite FM
- Save
Now in the Sonos app:
- Go to Browse > TuneIn > My Radio Stations
- Poolsuite FM should appear in the list
- Tap to play on any room or group
Important: Your Sonos speakers need to be able to reach the machine running the bridge on your local network. The TuneIn custom URL is just a pointer — the audio streams directly from your machine to the Sonos speakers.
If you have the Sonos HTTP API (e.g. node-sonos-http-api) running, you can start playback directly:
curl "http://SONOS_API_IP:5005/ROOM_NAME/setavtransporturi/http://YOUR_IP:8489/stream"- Open the Sonos desktop app (Windows or macOS — not the mobile app)
- Go to Manage > Add Radio Station
- Enter the stream URL:
http://YOUR_IP:8489/stream - Name it Poolsuite FM
- Click OK
The station appears under My Radio Stations and is accessible from all Sonos apps.
Note: Sonos supports ICY metadata, so track titles will display in the app and on Sonos displays (e.g. Era 300, Move 2).
python3 main.py [options]
Options:
-c, --config FILE Path to config JSON file
-p, --port PORT HTTP server port (default: 8489)
--host HOST Bind address (default: 0.0.0.0)
--no-shuffle Play tracks in playlist order
--playlist NAME Filter to a specific Poolsuite playlist
-v, --verbose Enable debug logging
# Verbose logging (recommended for first run)
python3 main.py -v
# Custom port
python3 main.py --port 9000
# Only play tracks from the "Indie" channel
python3 main.py --playlist "Indie"
# Use a config file
python3 main.py --config config.jsonThe bridge includes a retro Miami 80s-styled stereo interface at http://YOUR_IP:8489/ with dark and light themes:
- LCD display with scrolling marquee showing the current track (linked to SoundCloud)
- Transport controls — Previous, Play/Pause, Next
- Analog channel selector dial that animates when switching channels
- Channel buttons to switch between Poolsuite playlists on the fly
- In-browser audio player for listening directly
- Play history with SoundCloud links for each track
- Copy-to-clipboard buttons for all endpoint URLs
| Endpoint | Description |
|---|---|
/ |
Web UI — retro stereo interface with transport controls and play history |
/stream |
MP3 audio stream (this is what you add to Roon) |
/stream.mp3 |
Alias for /stream |
/skip |
Skip to the next track (GET or POST) |
/prev |
Go back to the previous track (GET or POST) |
/channel?name=X |
Switch channel; omit name to list available channels (GET) |
/status |
JSON API — now playing, listeners, uptime |
Roon's transport controls (next/previous) don't work with radio streams. Use these instead:
- Web UI: Open
http://YOUR_IP:8489/and use the PREV / NEXT transport buttons - API:
curl http://YOUR_IP:8489/skiporcurl http://YOUR_IP:8489/prev - macOS Shortcut: Create a Shortcuts automation that fetches the
/skipor/prevURL, then assign a keyboard shortcut - Home Assistant / Streamdeck: Call the
/skipor/prevendpoint as an HTTP action
Switch between Poolsuite playlists without restarting:
- Web UI: Click any channel button below the dial, or use the
--playlistCLI flag for the initial channel - API:
curl http://YOUR_IP:8489/channel?name=Indie - List channels:
curl http://YOUR_IP:8489/channel(returns JSON with available channels and current selection)
Copy config.example.json to config.json and edit as needed:
{
"host": "0.0.0.0",
"port": 8489,
"bitrate": "192k",
"crossfade_seconds": 3,
"shuffle": true,
"playlist_filter": null
}| Key | Default | Description |
|---|---|---|
host |
"0.0.0.0" |
Bind address |
port |
8489 |
HTTP server port |
bitrate |
"192k" |
MP3 output bitrate |
format |
"mp3" |
Output audio format |
crossfade_seconds |
2 |
Seconds of silence between tracks |
shuffle |
true |
Randomize track order |
poolsuite_api |
"https://api.poolsidefm.workers.dev" |
Poolsuite API base URL |
playlist_filter |
null |
Only play tracks from playlists matching this name |
sudo tee /etc/systemd/system/poolsuite-roon.service << 'EOF'
[Unit]
Description=Poolsuite FM for Roon
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=YOUR_USER
WorkingDirectory=/path/to/poolsuite-roon-streaming
ExecStart=/usr/bin/python3 main.py --config config.json
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now poolsuite-roon.service
# Check status
sudo systemctl status poolsuite-roon.service
# View logs
journalctl -u poolsuite-roon.service -fcat > ~/Library/LaunchAgents/com.poolsuite.roon.plist << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.poolsuite.roon</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/python3</string>
<string>main.py</string>
</array>
<key>WorkingDirectory</key>
<string>/path/to/poolsuite-roon-streaming</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
EOF
launchctl load ~/Library/LaunchAgents/com.poolsuite.roon.plist┌──────────────────────────────────────────────────────┐
│ main.py — Orchestrator │
│ Fetches playlists, resolves tracks, manages queue │
│ Pre-resolves next track while current plays │
│ Feeds realtime silence to keep stream alive in gaps │
│ Handles skip, previous track, and channel switching │
├──────────────────────────────────────────────────────┤
│ poolsuite_client.py — Poolsuite API Client │
│ GET /v1/get_tracks_by_playlist → playlist + tracks │
│ GET /v2/get_sc_mp3_stream?track_id=X → audio URL │
│ Rate limiting with exponential backoff │
├──────────────────────────────────────────────────────┤
│ audio_pipeline.py — Audio Pipeline │
│ yt-dlp: resolve SoundCloud → direct audio URL │
│ ffmpeg master encoder: continuous 192k MP3 stream │
│ ffmpeg decoder: per-track PCM fed into master encoder│
├──────────────────────────────────────────────────────┤
│ stream_server.py — HTTP Radio Server │
│ /stream: continuous MP3 with ICY metadata injection │
│ /skip, /prev: track navigation │
│ /channel: playlist switching │
│ /status: JSON now-playing info │
│ /: retro stereo web UI with transport + history │
├──────────────────────────────────────────────────────┤
│ template.html — Retro Miami 80s Web UI │
│ LCD marquee, transport controls, analog channel dial │
│ Play history with SoundCloud links, dark/light theme │
└──────────────────────────────────────────────────────┘
│
▼
Roon (Live Radio)
"yt-dlp not found" / "ffmpeg not found" Install the missing dependency. Keep yt-dlp updated — SoundCloud extractors break periodically:
pip3 install -U yt-dlpTracks skipping or failing to resolve
SoundCloud URLs expire and rate limits apply. The bridge retries with backoff and pre-resolves the next track to minimize gaps. Run with -v for detailed logs.
"Address already in use" on startup A previous instance is still running. Kill it:
lsof -ti :8489 | xargs kill -9Roon says "could not find a radio station at this URL"
- Make sure the bridge is running and tracks are actively streaming (check the terminal logs)
- Use your machine's actual local IP, not
0.0.0.0orlocalhost - Ensure your Roon Core can reach the bridge (same network, port not firewalled)
- Test by opening
http://YOUR_IP:8489/in a browser first
Stream dies between tracks This was a known issue that's been fixed. Make sure you're on the latest version. The bridge now pre-resolves the next track and pumps silence during transitions.
Rate limiting (429 errors in logs) The bridge automatically retries with exponential backoff (2s, 4s, 8s). If you see persistent 429s, SoundCloud is throttling aggressively — the bridge will recover on its own.
The Poolsuite API provides several curated channels. Use --playlist to filter:
- Poolsuite FM — The flagship mix
- Indie — Indie poolside vibes
- Balearic — Mediterranean chill
- Tokyo — Japanese city pop and funk
- Friday — Weekend starters
- Hangover — Sunday recovery
- Mixtapes — Guest-curated long mixes
- Poolsuite for the incredible curation
- Music is sourced from SoundCloud — support the artists
- Built for Roon home audio systems
- Powered by yt-dlp and ffmpeg
MIT — for personal use. Please respect SoundCloud's and Poolsuite's terms of service.



