# radieo A personal music radio: an always-on HTTP audio stream, automatically fed from several sources and broadcast with [Liquidsoap](https://www.liquidsoap.info/). 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 at `GET /next`. *(currently it only serves the cache directory; the download providers come in later milestones — see roadmap)* - **`stream`** (Liquidsoap) — deliberately dumb. It pulls the next track from the `ingest` daemon, broadcasts the audio over HTTP, and never goes silent thanks to a local cache fallback. Playback sources (planned): a [Navidrome](https://www.navidrome.org/) library via the OpenSubsonic API, arbitrary tracks fetched with [yt-dlp](https://github.com/yt-dlp/yt-dlp) (Bandcamp, SoundCloud, YouTube…), and listening suggestions from a ListenBrainz RSS feed. ## Usage Requirements: Docker with Compose v2. ```sh # 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). ## Configuration Copy `.env.example` to `.env` and fill in your Navidrome details: ```sh cp .env.example .env # edit .env: RADIEO_NAVIDROME_URL / USER / PASSWORD / PLAYLIST ``` If the Navidrome variables are left empty, the source is simply disabled and the stream plays whatever is already in `cache/` (the milestone-1/2 behaviour). ## Current status **Milestone 3 — Navidrome provider: done.** - `ingest` pulls tracks from an OpenSubsonic playlist (Navidrome), downloading them into the shared cache ahead of playback (prefetch buffer). - Play history and LRU retention are tracked in a SQLite database under `state/`: only the N most recently played files are kept on disk (`RADIEO_RETENTION_KEEP`, default 20); anti-repeat avoids replaying a track seen among the last plays. - `GET /next` returns the next track as an annotated Liquidsoap URI with real title/artist metadata (or an empty body when nothing is ready). - `stream` (Liquidsoap v2.4.5) pulls via `request.dynamic` and falls back to the local `cache/` directory; `mksafe` guarantees silence rather than a crash. - HTTP stream served at `http://localhost:8000/radio.mp3` (MP3, 192 kbps), multiple simultaneous listeners supported. The yt-dlp and ListenBrainz sources come next. ## 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.