radieo/README.md
Pierre-Olivier Mercier 29ab0be7cb Milestone 1: Liquidsoap broadcasting skeleton
Liquidsoap (v2.4.5) container that plays the /cache directory in random
order and broadcasts it over HTTP at :8000/radio.mp3 (MP3 192 kbps).
mksafe guarantees a continuous stream (silence when the cache is empty).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 16:14:14 +08:00

2.8 KiB

radieo

A personal music radio: an always-on HTTP audio stream, automatically fed from several sources and broadcast with Liquidsoap.

The goal is a hassle-free stream that always has something playing, where the next track is picked automatically. It is meant for personal use (a couple of simultaneous listeners), not for public broadcasting.

How it works

radieo is built as two layers, each running in its own Docker container and sharing a cache volume:

  • ingest (Python) — the brain. It decides what to play next, resolves and downloads tracks into a local cache, keeps a pre-filled queue, and exposes the next track over HTTP. (planned — see roadmap below)
  • stream (Liquidsoap) — deliberately dumb. It broadcasts the audio over HTTP and never goes silent thanks to a local fallback.

Playback sources (planned): a Navidrome library via the OpenSubsonic API, arbitrary tracks fetched with yt-dlp (Bandcamp, SoundCloud, YouTube…), and listening suggestions from a ListenBrainz RSS feed.

Usage

Requirements: Docker with Compose v2.

# Drop some .mp3 files into the cache directory
cp /path/to/music/*.mp3 cache/

# Build and start the stream
docker compose up -d

# Listen (VLC, a browser, any audio player)
#   http://localhost:8000/radio.mp3

Stop it with docker compose down.

The stream is MP3 at 192 kbps. Multiple clients can listen at the same time. New files dropped into cache/ are picked up automatically (the playlist is reloaded when the directory changes).

Current status

Milestone 1 — broadcasting skeleton: done.

  • Liquidsoap (v2.4.5) container plays the cache/ directory in random order.
  • HTTP stream served at http://localhost:8000/radio.mp3 (MP3, 192 kbps).
  • Continuous output guaranteed: silence rather than a crash when the cache is empty (mksafe).
  • Multiple simultaneous listeners supported.

At this stage the playlist is filled manually; the automatic ingestion layer is not implemented yet.

Roadmap

  1. Broadcasting skeleton — Liquidsoap serving the cache directory.
  2. Ingestion daemon — Python daemon exposing GET /next; Liquidsoap switches to a request.dynamic source with the cache as fallback.
  3. Navidrome provider — play from an OpenSubsonic playlist, with caching, LRU retention and play history.
  4. yt-dlp provider — fetch tracks from a maintained URL/artist list; weighted mixing between sources.
  5. Canonicalizer — ListenBrainz MBID lookup for source-agnostic de-duplication.
  6. ListenBrainz provider — parse the RSS suggestions feed and resolve each one to Navidrome or yt-dlp.
  7. Polish — crossfade, robustness, optional web player, config file.