πΊ HospAir Roadmap
Every phase of the product: shipped, live, and planned.
Last updated: 18 June 2026 Β· Related: Engine roadmap Β· Docs index Β· Main README
Product vision
- One Docker stack per hospital site
- Volunteer-friendly browser studio (Myriad-style)
- Reliable log β audio alignment (log is source of truth; audio follows)
- End state: schedule β show β clock β generated hour log β playout
Status at a glance
| ID | Name | Status |
|---|---|---|
| 0 | Greenfield MVP (stack, library, playout, streams) | β Done |
| 0.1 | Myriad-style studio & library UX | β Done |
| 0.2 | Hour log, scheduling columns, drag-to-log | β Done |
| 0.3 | Automated backtiming & hour lock | β Done |
| 0.4 | Early playout reliability (sync, carts) | β Done |
| 1 | Customizable dual-monitor playout | β Done |
| 2 | Layout presets (user + station) | β Done |
| 3 | Studio parity, SmartInfo, voice links | β Done |
| 3.1 | SmartInfo polish (traffic, shortcuts) | β Done |
| 4A | Go button & manual start | β Done |
| 4B | SWEEP segues, marker trim, warnings | β Done |
| 4C | Top-of-hour auto backtiming | β Done |
| 4D | Advert breaks & basic trafficking | β Done |
| 4E | Admin hub, sample.html UI polish | β Done |
| 5 | Playout desync hardening | β Done |
| 5.1 | Playout playhead sync (Liquidsoap 2.3) | β Done |
| 5.2 | Dead-air protection & stream watchdog | β Done |
| 5.3 | Liquidsoap 2.4.x pull queue (request.dynamic) |
πΆ LIVE: PR 1β2 shipped & on air 12 Jun; hour-boundary landing + re-fill sync hardened 13 Jun (see below); PR 3 (delete push path) + PR 4 (24h soak) remain |
| 5.4 | Wall-clock catch-up & time-authoritative logs | β Done |
| 5.5 | Liquidsoap event push (track / dead-air webhooks) | β Done |
| 6.1 | Scheduling admin hub | β Done |
| 6.2 | Show format policies & genre hour fill | β Done |
| 6.3 | Clock templates | β Done |
| 6.4 | Clock-driven hour log generator | β Done |
| 6.5 | Campaign trafficking & rotation | β Done |
| 6.6 | Schedule rules & auto hour build | β Done |
| 6.7 | News feeds (hourly import, clock NEWS) | β Done |
| 6.8 | Studio live mic (arm, VAD duck, monitor) | β Done |
| 6.9 | Myriad log/library UX (menus, track info) | β Done |
| 6.10 | Library metadata (genres, facets, normalize) | β Done |
| 6.11 | Presenter show prep (studio briefing panel) | β Done |
| 6.12 | Dedicated track edit window (Myriad-style) | β Done (phase 1) |
| 6.13 | Cross-hour no-repeat (station playout setting) | β Done |
| 6.14 | Request system (public form, moderation) | β Done |
| 6.15 | Playlist editor & show builder | β Done |
| 7A | Play log export & compliance CSV | β Done |
| 7A+ | Reports hub rebuild (ISRC/album enrichment, track summary, PDF/XLSX/CSV) | β Done |
| 7B | AI / log-aware voice links | β¬ Planned: folded into Phase 8C (the AI presenter); reuses the 7E voice-track segue |
| 7C | On-air time-stretch (backtiming) | β Done (12 Jun): AutoFade playbackRate applied physically via process:/atempo in the pull URIs |
| 7D | E2E playout test suite | βΈ On hold: until feature-complete (13 Jun; surfaces still changing) |
| 7E | Voice tracking pro | β
Done (12 Jun): auto-trim recorder, Myriad-style segue VT into the log (recorded overlap reproduced on air), remote VT page /studio/voice-track |
| 7F | Remote guest contributions (CleanFeed-style) | β¬ Planned |
| 7G | Hooks & intro/outro detection | β¬ Partial: worker hints on ingest; full detection TBD |
| 7H | Hospital content system | β Done |
| 7I | WebRTC program monitor (sub-300ms) | β¬ Planned |
| 7J | Storage optimisation (single MP3 + cache) | β¬ Planned |
| 7K | Multi-station manager | β¬ Planned |
| 7L | Live ops hardening (harbor health, webhooks) | β¬ Planned |
| 7M | Stream relays (external Icecast/Shoutcast distribution) | β Done |
| 7N | Listener statistics (snapshots, TTSL, trends) | β Done |
| 7O | Media tags (tag pools, playlist tag rows, automation resolution) | β Done |
| 7P | Podcast sources (RSS subscribe, auto-download, auto-import) | β Done |
| 7J | Storage optimisation (single copy + cache) | πΆ Mostly done (15 Jun): two-mode store shipped: local /data/audio home by default, or external S3 home + size-capped local LRU cache; MinIO removed, no on-disk doubling. Remaining: tune cache eviction under real disk pressure |
| 7Q | Segue editor pro (wavesurfer multitrack) | β Done (18 Jun): drag-overlap multitrack editor, drawn per-track volume envelopes that play on air (clock-safe time-varying amplify), and in-editor voice-link recording with auto-duck. Phases 1β4; see below |
| 7R | Real waveforms & studio telemetry | β
Done (17 Jun): ingest-time mono peaks (Track.waveformData), SoundCloud-style canvas renderer + decode/prefetch cache, play-duration telemetry, unattended recovery |
| 7S | Per-presenter remote DJ broadcasting (show-gated) | β
Done (18 Jun): per-DJ harbor logins (dynamic input.harbor auth), shows opt in + pick permitted DJs, evaluator fades the right DJ on air when their hour starts and back to the log when they leave; dashboard countdown + studio presence avatars; full user management (roles/active/delete). See below |
| 8 | AI audio creation & presenter | πΆ In progress: TTS foundation (ElevenLabs voice gen in Voice Studio) and LLM-drafted presenter scripts (DeepSeek, show/clock/day-aware) shipped 14 Jun; admin AI + integration keys in DB; live-link insertion (8C.4) remains (see below) |
Legend: β Done Β· β¬ Planned Β· βΈ On hold Β· Partial = started, not production-complete
Recently completed (Jun 2026)
Shipped 14β18 June
- Per-presenter remote DJ broadcasting (7S): remote DJs are no longer one shared password. Each presenter with Remote broadcast enabled (Admin β Users) gets a personal harbor username + password, validated by a dynamic
input.harborauthcallback (/api/internal/harbor-auth), the station-wide password remains a shared break-glass only. A show can be made remote-aware (Allow remote DJs + a permitted-DJ picker); a worker-tick evaluator applies the rule on-air = show.allowRemote AND a permitted DJ connected, so a DJ can connect early, fade on air when their hour starts, and fade back to the generated log when they leave or the show ends, with crossfade transitions on the program switch. It never overrides a manualREMOTE DJtakeover or emergency lock (Station.remoteShowAutoLive). The connected DJ sees a dashboard countdown (to-air / time-left); the studio header shows pulsing presence avatars (red on air, amber waiting). Plus full user management: edit roles, activate/deactivate, hard-delete (with self-lockout guards). Schema:User.canRemoteBroadcast/remoteLogin/remotePasswordEnc,Show.allowRemote+ShowRemotePresenter,LiveSource.connectedUserId/connectedLogin. - Segue editor pro (7Q): the old slider modal is replaced by a wavesurfer-multitrack editor. All tracks drag to set the overlap; you draw per-track volume envelopes over the waveforms and they play on air, applied by a clock-safe time-varying
amplifyinhospair_transition(envelopes stored astailEnvelope/headEnvelopebreakpoints +segueCrossfadeSeconQueueItem; no envelope β byte-identical to the classic transition). The mic records a voice link straight into the editor as a middle lane (auto-ducking the music); on save it uploads, waits for processing, and inserts the row between the songs via/api/queue/voice-track. Built in four phases (UI β persistence β live engine β voice recording). - AI creation, first slices (8C): ElevenLabs voice generation in the Voice Studio (TTS clip β mixer β save as a library track); LLM-drafted presenter scripts (DeepSeek) aware of the show, clock, day and time of day, shown in the studio show-prep pane; Admin AI settings + third-party integration keys (Last.fm, National Highways) stored in the DB. Key reuse path from the 8C plan held, generated audio rides the existing voice-track/clock machinery.
- Two-mode storage (7J), MinIO removed: replaced MinIO-as-home + local-cache (which doubled every track on disk) with: local mode (default) where
/data/audiois the permanent home, no object store; or S3 mode where an external bucket is the durable home and/data/audio-cacheis a size-capped LRU filled on demand and trimmed by the reaper. Ingest writes the original to/data/audio/raw, the worker transcodes locally and only uploads in S3 mode. Admin β Storage shows the mode + local usage and offers manual migrate (localβS3) and pull (S3βlocal). - Real waveforms & telemetry (7R): ingest generates mono peaks (
Track.waveformData); studio renders a SoundCloud-style canvas waveform with an in-memory decode/prefetch cache, play-duration telemetry, and unattended recovery. - Library shell:
/librarynav, column picker, and dashboard tweaks. - Station branding & privacy: station branding (stream art/name), Icecast fixes, and listener-metadata privacy controls.
- Scheduling & content: shows attach a pool/playlist with weighted music-vs-episode fill; per-content-pool play order (sequential / random / avoid-recent); drag-paint, relocate and right-click on the assignment grids; on-air show + clock highlighted in admin grids; a Station ID clock item drawn from the
IDlibrary category; show-end-aware content-pool picks.
Shipped 13 June (pull-engine reliability pass: branch fix/dead-air-truthful-hour-logs, on dev, pending soak)
- Hours land on :00, truthfully: fill no longer closes the tail with an oversized song that overshoots the boundary by minutes; end-of-hour gaps fill with a single best-fit song (smaller preferred) and at most one jingle/station ID as a last resort (never a stack of idents). Verified future hours land within Β±2s of :00.
- Advert breaks fill their reserved length: clock breaks reserve 120β180s but trafficking dropped in one ~30s spot, shrinking the slot and underrunning the hour by ~5 min. Breaks are now stacked to the reserved duration at generation time (distinct adverts β jingle/ID filler), so they survive regeneration (no trafficking-vs-generator churn) and the hour stays on :00. Root cause of the recurring over/underrun: the oversized tail song and the break under-fill were cancelling each other.
- Re-fill / clear no longer desyncs log β audio: a fill now always reconcile-then-syncs (re-derive now-playing from what Liquidsoap is actually airing, then replace only the pending tail), removing the gate that could reset the serve cursor out from under live audio on a clear+refill. (Partial progress toward 5.3 PR 3's "gentler reconcile.")
- Studio player handoff flap fixed: the now-playing widget no longer jumps next β previous β next during the crossfade window (stateless resolver reverted to the DB anchor at remaining=0); added forward-only hysteresis.
- Login splash background:
/images/was caught by the auth middleware matcher and 307-redirected, breaking the login background; excluded it.
Shipped 5β11 June (post previous roadmap update)
- Wall-clock catch-up (5.4): time-authoritative logs: hour rows keep frozen :00-anchored times; on startup, resync, and a routine tick the playhead fast-forwards to the row the wall clock says should be on air. Guards prevent skipping the next hour's leading rows or dragging the next hour in early. Unit-tested (
apps/web/src/lib/wall-clock-catchup.test.ts). - Liquidsoap event push (5.5): Liquidsoap now pushes events to the control server (
/internal/track-pushon track start,/internal/dead-air-pushon dead-air engage/clear) instead of relying purely on polling; control server fires webhooks to the web app. Control server also keeps a wall-clock playhead for when Icecast telemetry freezes. - Stream relays (7M): Admin β Stream relay: push output to external Icecast/Shoutcast servers; relay config stored on
Outputrows, rendered intorelay-outputs.liqbygenerate-relay-outputs.mjs; health + listener polling per relay. - Listener statistics (7N): periodic
ListenerSnapshotper output; Admin β Statistics with overview/historical/real-time tabs, peak/average/TTSL, CSV export. - Reports hub rebuild (7A+): playout log enriched with album, ISRC, category, playlist/tag name, presenter; track summary + recently played tabs; PDF/XLSX/CSV export.
- Media tags (7O): station-defined tags with colour; tracks assignable; playlists support TAG placeholder rows resolved to a rotating track at automation/queue-build time; library tag filtering.
- Voice Studio (7E phase 1): multi-track browser recorder/mixer for voice links: voice/music/SFX lanes, trim, per-track gain, fades, OfflineAudioContext mix-down to WAV, upload to library.
- Podcast sources (7P): RSS feed subscriptions with check interval; episode discovery, selective or auto download, auto-import to playlist or content pool; PODCAST track category.
- Playout stability fixes: duplicate log row dedupe (transition silence / mid-song skips), routine catch-up no longer skips queued items at transitions, AutoFade stretch budget credited in coverage (stops 10s re-stretch churn), next-hour generation no longer blocked on current-hour coverage, studio perf (single log scan per queue build, cache invalidation once per track change).
Playout sync & studio UI (5.1)
Major regression after Liquidsoap 2.3: playhead read delayed Icecast metadata first, causing log/UI desync and watchdog fast-forward.
radio.on_air_metatelnet command, authoritative playhead from automation bus (pre-Icecast)- Playhead priority:
radio.on_air_metaβrequest.metadataβ Icecast (file/id only, never title) - Faster track-change poll (500ms); filename settle reduced to 400ms
- Studio display: telemetry-led on-air when ahead of DB; predictive handoff (~2s before track end)
- Monotonic countdown: remaining time no longer bounces on noisy telemetry
- Queue invalidation when telemetry track id changes
isNextQueuedAlreadyOnAirfix, no false fast-forward whenremainingSecis null
Dead-air & streams (5.2)
- Dead-air detection β standby fallback engagement + webhooks
- Icecast output polling reliability fixes
Hour log & music rules (6.13)
- Admin β Playout: No repeat across hours (
hourLogNoRepeatHours, 0β168h) - Show-level
noRepeatHoursstill overrides per hour - Burn window uses playout history + active queue (played, skipped, queued)
Liquidsoap 2.3 migration (part of 5.1)
- Queue control API updates for LS 2.3
settings.request.deprecated_on_air_metadatafor telnet playhead reads- Stale queue/audio fixes, skip settling, expected-next reconciliation
Completed work (historical summary)
- Docker monorepo: web, worker, Liquidsoap, Icecast, Postgres, Redis, optional WebRTC bridge (audio on local disk, or an external S3 bucket, 7J; MinIO removed 15 Jun)
- Auth (NextAuth), RBAC, audit log, health endpoints
- Library upload β worker ingest (ffmpeg) β READY tracks
- Queue CRUD, Liquidsoap sync, playout webhooks, crossfade, control server
- Icecast public + wards; Harbor for remote encoders (
/live,/browser-live) - Hour log with schedule labels, drag-to-log, hour fill, auto fill at :45/:50
- Backtiming, hour lock, soft/absolute markers, SWEEP segues
- Cart overlay ducking, anchor sync,
replace_pending, emergency modes - Desync detection/Resync, catch-up heal, log-edit sync, studio-gated reconcile
- Timezone-aware top-of-hour terminal fade (marker trim + crossfade skip into :00)
- Dual/custom studio layouts, layout presets
- SmartInfo (wiki, weather, traffic), voice-link recorder, keyboard shortcuts
- Show Prep pane, on-air + next 8, backtiming pill, weather/news/traffic, guest notes
- Go / STANDBY / awaitingGo, backtiming warnings in UI
- Live mic: ARM MIC β PCM β Harbor; VAD duck; YOU (foldback) vs MONITOR (program ~200ms)
- Remote DJ via BUTT/Harbor
/live - Context menus on station log & library; Media Item Information popout
- Track edit window (
/track-edit): titling, timing, restrictions, waveform, FFmpeg tools - Log edit: insert palette, swap rows, play heat map
- Media wall, segue editor, studio monitor feed (OGG/Opus proxy)
- Scheduling hub: shows, weekly rules, playlists, clocks
- Genre/duration/no-repeat policies per show; clock editor with timeline
generateHourLog(), active show + clock (breaks, jingles, NEWS, hospital slots)- Campaign trafficking (
/admin/ad-breaks); auto hour log pre-window when enabled - News feeds: hourly URL import, clock NEWS rows, hard-cut playout
- Music rules: artist/title separation, burn hours, genre weighting, decade balance
- Genre normalization, multi-genre facets, batch normalize, studio filters
- Hospital content: HOSPITAL clock slot, pools, weighted rotation, expiry
- Request system: public
/requests, moderation, approve β log - Playlist editor (
/admin/playlists/[id]), apply running order to log - Play log CSV export (
/admin/reports)
Outstanding backlog
Priority 1: Reliability (do before new features)
| ID | Item | Notes |
|---|---|---|
| 5.3 | Liquidsoap 2.4.4 pull queue | Replace telnet push/flush with HTTP snapshot + request.dynamic; enables stable crossfade: liquidsoap-roadmap.md |
| 7D | E2E playout test suite | βΈ On hold until feature-complete (13 Jun). Scope when resumed: pure invariant tests (hours land on :00, breaks fill reserved length, no oversized tail, re-fill preserves on-air row) β DB-backed generateHourLog integration β snapshot-API smoke β npm test glob + GitHub Actions |
| 7L | Live ops hardening | Fix live webhook schema; harbor connect presence; per-session tokens; liveHealthTimeoutSec. Note: dead-air detection + webhook already exist (5.5): remaining gap is routing them to human notifications (ntfy/email) |
| 7C | On-air time-stretch | ffmpeg atempo via queue URI after 5.3 pull queue ships; unlocks softer backtiming |
| - | PlayoutLog catch-up | Ensure history panel stays in sync when webhooks lag (optional PlayoutLog writes on heal) |
| - | Deploy discipline | Rebuild migrate image whenever schema changes (prevents /api/station 500) |
| - | Top-of-hour fade reliability | Surfaced 13 Jun: the boundary FADE is scheduled but occasionally never fires (one hour ran its tail full instead of fading at :00): the on-air safety net. Investigate checkTopOfHourFade timer in control-server.mjs. |
| - | Live-hour top-up churn | Surfaced 13 Jun: the live hour's tail is rewritten on most worker ticks (planned-vs-playout projection). Cosmetic now (lands on :00) but the documented dead-air trigger: make the live top-up idempotent. |
| - | Backtiming drift display | Surfaced 13 Jun: DRIFT can read +3600s / always-red transiently when next-hour rows are momentarily untagged during re-fill churn (projectLogEndBeforeTarget). Self-corrects; cosmetic. |
| - | LS 2.4.x prefetch desync | Editing the immediate-next track can mismatch logβaudio for one track (engine can't flush an already-prefetched track): architectural; minimise by not replacing the prefetched row on re-fill. |
Priority 2: Studio polish
| ID | Item | Notes |
|---|---|---|
| - | Dual/custom studio live | Wire ARM MIC + bridge to /studio/dual and /studio/custom |
| - | Myriad log edit polish | Replace/insert row types, multi-select swap |
| 6.12+ | Track edit phase 2 | Richer ribbon; per-clock restriction rules |
| 7G | Hook detection | Refine worker peak analysis; manual re-analyse in edit window |
| 7I | WebRTC program monitor | Sub-300ms MONITOR (today ~200ms via Icecast proxy) |
Priority 3: Audio & remote
| ID | Item | Notes |
|---|---|---|
| 7F | Remote guest contributions | CleanFeed-style: see phased plan below |
| 7E | Voice tracking pro | Auto-trim links, segue preview, remote VT |
| - | Audio processor | Station AGC/limiter/EQ in Liquidsoap |
Priority 4: Platform & scale
| ID | Item | Notes |
|---|---|---|
| 7J | Storage optimisation | πΆ Mostly done: two-mode store shipped (local home, or external S3 home + local LRU cache; MinIO removed). Remaining: tune AUDIO_CACHE_MAX_BYTES eviction under real disk pressure |
| 7K | Multi-station manager | Central admin, shared library option |
| 7B | AI voice links | Log-aware generated links: now Phase 8C.4 (see below) |
| 8 | AI audio creation & presenter | Creation studio first (8C); schedule assistant (8A/8B) deferred |
Phase 7F: Remote guest contributions (CleanFeed-style)
Today: one studio mic (ARM MIC over automation) and one remote DJ takeover (Harbor /live). No invite links, guest roster, mix-minus, or talkback.
| Sub-phase | Goal | Key work |
|---|---|---|
| 7F.0 | Harden live ops | Webhook schema, harbor presence, scoped tokens (7L) |
| 7F.1 | Single guest MVP | Harbor /guest-1, invite token API, guest join page, host mute fader |
| 7F.2 | Mix-minus | Liquidsoap monitor bus (program minus guest); downlink to guest browser |
| 7F.3 | Talkback + roster | Host TALK β guest private; join/leave/mute UI; audit log |
| 7F.4 | Multi-guest WebRTC | SFU (LiveKit/mediasoup) + TURN; per-guest levels; Opus path |
Builds on: Harbor ingest, webrtc-bridge WSβffmpeg pattern, VAD ducking, RBAC.
Phase 8: AI audio creation & presenter
Current focus = the creation studio (8C). Let users generate jingles, station IDs, and "live" presenter links, script + voice, and drop them into the library/log. The schedule assistant (8A/8B) is deferred behind it.
Key reuse: this is not a new playout path, and mostly not new plumbing. A generated
link is just a voice track whose audio came from TTS instead of a mic, so it rides the
existing 7E segue machinery (segueOverlapSec on QueueItem,
/api/queue/voice-track). Generated
IDs/jingles are ordinary library tracks (ID/JINGLE) that feed the clock slots already
built (STATION_ID/JINGLE). The only genuinely new capability is text β speech.
Decisions (14 Jun): TTS = ElevenLabs (realism + low latency + voice cloning). "Jingles" = spoken-over-bed now, sung/produced jingles a later phase. Voices = stock now, presenter cloning a later opt-in.
8C: AI audio creation studio (build slices)
| Slice | Goal | Key work |
|---|---|---|
| 8C.1 | TTS foundation + Station IDs | β
Shipped 14 Jun: ElevenLabs voice generation in voice-studio.tsx: generate speech β clip in the mixer β save as a library track (feeds the STATION_ID/ID slot). Provider key + AI settings live in Admin (DB), not env. |
| 8C.2 | Spoken-over-bed jingles/sweepers | Layer generated speech over a BED track and mix down (the Voice Studio already does OfflineAudioContext mixdown, voice-studio-audio.ts); save as JINGLE. |
| 8C.3 | LLM drafts the script | β Shipped 14 Jun (provider: DeepSeek, not Claude): presenter scripts aware of the show, clock, day and time of day, drafted server-side and shown in the studio show-prep pane; editable. |
| 8C.4 | "Live presenter before a track" | β¬ Remaining glue: Studio log β Add AI link before β context (prev/next track) β script (editable) β TTS β insert VT row that talks over the next intro (7E). Script-drafting (8C.3) + TTS (8C.1) + in-editor voice-link insertion (7Q) now all exist; wire them together. The headline feature. |
| 8C.5 | Presenter voice cloning | Opt-in per presenter: recorded consent β ElevenLabs clone β map presenter β voiceId. |
| 8C.6 | Sung / produced jingles | Melodic jingles via a music-generation model (Suno/Udio-class): separate provider, later phase. |
8A/8B: schedule assistant (deferred)
| Sub-phase | Goal | Key work |
|---|---|---|
| 8A | Schedule assistant: read-only | Admin chat; claude-opus-4-8 + tool-use loop over existing queries (get_request_summary, find_underfilled_hours, explain_log_row). No on-air impact. |
| 8B | Assistant: gated writes | Dedicated tools (propose_clock_edit / fill_hour / swap_track) with confirm-before-apply on anything touching the live log. Claude API tool runner, not Managed Agents (tools are our own lib ops). |
Guardrails (carried from existing lessons)
- Degrade gracefully: a missing/failed TTS render must never reach air as dead air, an unfilled ID/jingle slot falls back to music, same as an empty jingle slot today. With no
ELEVENLABS_API_KEYthe generate controls disable cleanly with a clear message. - On-air contract: generated audio goes through the same normalisation/cue/duck rules as recorded voice tracks, generation must not bypass loudness/segue handling.
- Editorial scope: hospital station, live presenters are part of the value; AI links target unstaffed hours and automation, not daytime shows.
Builds on: 7E voice-track segue + Voice Studio mixer, clock item types (6.3 + the STATION_ID pattern), ID/JINGLE/BED categories, track upload/ingest, clock-driven generator (6.4).
What to focus on next
7D (test suite) is on hold until the feature set is complete. Decision 13 Jun: new features keep reshaping the surfaces a test would assert against (hour fill, projections, sync), so investing in tests now means re-writing them as features land. Build features to "feature-complete" first, then lock behaviour in with 7D. Until then, keep verifying by ear and record regressions in the issues table.
Now (this week)
- Verify the segue editor (7Q) by ear: record a voice link into a real upcoming segue and confirm: the green voice lane appears and drags, the music auto-ducks, and the drawn envelopes play on air where they were drawn (sub-second buffering offset is expected). Built + deployed 18 Jun; not yet exercised on a live segue.
- AI live-link insertion (8C.4): wire LLM script (DeepSeek) β ElevenLabs TTS β inserted voice-track row that talks over the next intro. The script-drafting and TTS pieces now exist; the studio "Add AI link before" flow is the remaining glue.
- 24h soak on the pull engine (5.3 PR 4 / LS-8): boundaries land on :00 and re-fill no longer desyncs; soak it while watching the fade-reliability and live top-up churn items above.
- Top-of-hour fade reliability: the boundary fade is the on-air safety net and it intermittently didn't fire (13 Jun). Worth a focused look before the soak is trusted.
Next (1β2 weeks)
- 5.3 PR 3, delete the push path: remove
pushTrack/mergeCommittedItems/orphan-heal heuristics (~800 lines) and calm the web's forceResync storms. The 13 Jun "always reconcile-then-sync" change is a step toward this; finishing it removes a class of churn. - 7L, Live ops hardening: dead-air webhooks exist; route them to human notifications. Unblocks guest work, makes Admin β Broadcast trustworthy.
- Volunteer studio pass: real shows on
/studio: mic, Go/Skip, hour lock, news slots, show prep, track edit. - Re-pin Liquidsoap to v2.4.5 when it releases (currently rolling 2.4.5-dev digest).
Then (2β4 weeks)
- 7J, Storage optimisation: disk pressure at scale; staging cache only.
- 7C, On-air time-stretch: after 5.3 pull queue is stable.
- 7F.1, Single guest: if live interviews are a near-term need.
Defer
- AI (7B, 8), multi-station (7K), full WebRTC monitor (7I), audio processor, until core playout is boringly reliable.
Architecture
flowchart TB
subgraph admin [Admin / Scheduling]
Grid[Schedule grid]
Show[Show + format policies]
Clock[Clock templates]
News[News feeds]
Gen[Hour log generator]
end
subgraph studio [Studio]
Log[Station log]
Mic[ARM MIC + monitor]
Transport[Go / Skip / Pause]
Carts[Media wall / carts]
end
subgraph engine [Engine]
Web[Next.js API]
LS[Liquidsoap]
IC[Icecast]
end
Grid --> Show --> Clock --> Gen --> Log
News --> Clock
Log --> Web --> LS --> IC
Mic --> LS
Playhead (5.1): radio.on_air_meta β DB queue β studio UI (telemetry leads near transitions).
Operations & docs
| Topic | Location |
|---|---|
| Liquidsoap engine plan | liquidsoap-roadmap.md |
| Volunteer runbook | OPERATIONS.md |
| Deploy & migrations | README, OPERATIONS |
| Nginx / Cloudflare | NGINX-PROXY.md, CLOUDFLARE-DNS.md |
| Studio | /studio, /studio/dual, /studio/custom |
| Admin | /admin/* |
| News feeds | /admin/news |
| Playout settings | /admin/playout |
| Live / remote DJ | Admin β Broadcast; Studio β ARM MIC / Remote DJ |