Milestone 2: ingestion daemon driving the stream
Add the Python `ingest` container exposing `GET /next`, which returns the next track as an annotated Liquidsoap URI (or an empty body when nothing is ready). Liquidsoap switches from a static playlist to a `request.dynamic` source pulling from the daemon, with the local cache as fallback and mksafe for guaranteed continuous output. For now the daemon just cycles through the files already in the cache; the download providers (Navidrome, yt-dlp, ListenBrainz) come in later milestones. Also commit the implementation plan (PLAN.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
29ab0be7cb
commit
f8eb0655eb
9 changed files with 247 additions and 19 deletions
24
README.md
24
README.md
|
|
@ -14,9 +14,11 @@ 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.
|
||||
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
|
||||
|
|
@ -46,21 +48,25 @@ reloaded when the directory changes).
|
|||
|
||||
## Current status
|
||||
|
||||
**Milestone 1 — broadcasting skeleton: done.**
|
||||
**Milestone 2 — ingestion daemon: done.**
|
||||
|
||||
- Liquidsoap (v2.4.5) container plays the `cache/` directory in random order.
|
||||
- `ingest` (Python) container exposes `GET /next`, returning the next track as
|
||||
an annotated Liquidsoap URI (or an empty body when nothing is ready).
|
||||
- `stream` (Liquidsoap v2.4.5) pulls from `ingest` via a `request.dynamic`
|
||||
source, and falls back to the local `cache/` directory when the daemon has
|
||||
nothing to offer.
|
||||
- HTTP stream served at `http://localhost:8000/radio.mp3` (MP3, 192 kbps).
|
||||
- Continuous output guaranteed: silence rather than a crash when the cache is
|
||||
- Continuous output guaranteed: silence rather than a crash when everything is
|
||||
empty (`mksafe`).
|
||||
- Multiple simultaneous listeners supported.
|
||||
|
||||
At this stage the playlist is filled manually; the automatic ingestion layer is
|
||||
not implemented yet.
|
||||
At this stage the daemon just cycles through the files already in `cache/`; the
|
||||
download providers (Navidrome, yt-dlp, ListenBrainz) come next.
|
||||
|
||||
## Roadmap
|
||||
|
||||
1. ✅ **Broadcasting skeleton** — Liquidsoap serving the cache directory.
|
||||
2. **Ingestion daemon** — Python daemon exposing `GET /next`; Liquidsoap
|
||||
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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue