ingest: give every track a source link

The player's "source" link only worked for direct yt-dlp URLs. Two other
cases had no linkable page: ListenBrainz picks resolved via ytsearch1: (the
locator is a search query) and Subsonic library tracks (an opaque song id).

Centralise the rule in Track.page_url and cover both: the yt-dlp fetcher now
records the concrete video URL it resolved into source_url, and a Subsonic
track links to the stream's new /share endpoint, which asks ingest to mint a
public share (createShare) on demand and redirects to it — so a share is only
created when a listener actually clicks, never per played track.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nemunaire 2026-07-04 11:42:29 +08:00
commit efd7307cc6
9 changed files with 154 additions and 18 deletions

View file

@ -30,8 +30,10 @@ simultaneous listeners), not for public broadcasting.
jingles (noon, snack time, ...) played once when their time comes.
- **Smooth playback**: a 3 s crossfade between tracks.
- **Built-in web player** at `http://localhost:8000/`: now playing (linked to
its source page), track history, skip button, per-track download, volume
memory, live auto-reconnect, prefetch progress, and a synthwave look.
its source page — a Bandcamp/YouTube page for yt-dlp tracks, or an on-demand
Subsonic share for library tracks), track history and the upcoming queue,
skip/restart controls, per-track download, volume memory, live auto-reconnect,
prefetch progress, and a synthwave look.
- **OS media controls**: the player exposes current track metadata and
play/pause/next through the Media Session API, so it wires into system media
controls (MPRIS on Linux, macOS Control Center, Windows, mobile lock screen)
@ -68,7 +70,9 @@ Fill in `.env`:
- **OpenSubsonic server**: `RADIEO_SUBSONIC_URL` / `USER` / `PASSWORD` and the
playlist to broadcast in `RADIEO_SUBSONIC_PLAYLIST` (name or id). Works with
any OpenSubsonic-compatible server (Navidrome, Gonic, Airsonic…). Leave empty
to disable this source.
to disable this source. To make the player's "source" link work for library
tracks, enable sharing on the server (Navidrome: `ND_ENABLESHARING=true`); the
link then mints a public share on demand, only when clicked.
- **Mix**: `RADIEO_WEIGHT_SUBSONIC` / `RADIEO_WEIGHT_YTDLP` /
`RADIEO_WEIGHT_LISTENBRAINZ` set the relative draw weight of each source
(`0` disables one).
@ -137,14 +141,17 @@ time, and serves it over an internal HTTP API:
- A *prefetch queue* keeps a few ready tracks; a SQLite database under `state/`
holds play history, the MBID cache, and LRU cache-file retention.
- API: `GET /next` (annotated Liquidsoap URI), `/fallback.m3u` (already-aired
tracks), `/status` (prefetch progress), `/healthz`.
tracks), `/status` (prefetch progress), `/queue` (upcoming tracks),
`/healthz`, and `POST /share?id=<songId>` (mint a Subsonic share on demand).
**`stream`** (Liquidsoap) pulls `/next` via `request.dynamic`, inserts jingles
(a `switch` that also handles the time-of-day slots), applies the crossfade and
`mksafe`, and outputs the MP3. On the same harbor port 8000 it also serves the
web player and its API: `/nowplaying`, `/history`, `/skip`, `/download`, plus
`/ingest/status`.
web player and its API: `/nowplaying`, `/history`, `/skip`, `/restart-track`,
`/download`, and — proxied from `ingest``/queue`, `/ingest/status`, and
`/share` (which forwards to `ingest`, then redirects to the created share).
Only port **8000** is published to the host. The browser never talks to `ingest`
directly — the Liquidsoap harbor acts as a small reverse proxy for the data the
player needs (e.g. `/ingest/status`), keeping everything on a single origin.
player needs (e.g. `/ingest/status`, `/queue`, `/share`), keeping everything on a
single origin.