Live Manager (live-manager.js)
The Live Manager decides who is live and routes them onto the broadcast
channel. It ranks candidate streams, restreams the winner into the remote
channel_live harbor (:8006), and watches over the whole chain.
Stream registry
Streams live in station-config.json → live.streams (survives restarts). A
stream is:
{
id: 'LIVE211E5A4A', // 8-char hex id
url: 'https://.../stream.m3u8',
title: 'Rooftop Session',
creator: 'Nadia',
listeners: 12,
votes: 3, // cumulative delta
ingest: 'rtmp' | 'rtmp2' | 'webrtc' | 'pipe', // normalized
addedAt: 1744567890123,
lastLiveAt: 1744568890123
}
Deduplication (important)
register() dedupes by url + title + normalized ingest. The ingest type is
normalized first: WHIP pipes (url starts with pipe: or the source is a pipe)
become 'pipe'; everything else defaults to 'rtmp'. This fixed a bug where
every WebRTC registration compared raw 'webrtc' against a stored 'pipe' and
stacked a duplicate stream each time. Re-registering the WHIP source now returns
the same id and the registry stays at one entry.
Ranking & selection
When asked to go live, the manager scores the streams:
- Freshness — recency of
lastLiveAt(been live recently = higher). - Listeners — current audience.
- Votes — cumulative votes.
- Blacklist — a stream marked dead (
blacklisted) is excluded.
The top-ranked candidate becomes the channel-live source; if no live stream is candidate-worthy, the channel returns to the normal mix.
The restream pipeline
- Probe —
_probeHarbor()checkshttp://10.10.8.230:8089/whip-info(WHIP server,live:true) to confirm a remote feed is actually flowing before declaring the channel live. - ffmpeg — restreams the winning source with
libmp3lame(128 kbps stereo) into the Liquidsoap harbor:icecast://source:hackme@10.10.8.230:8006/channel_live. - Monitor — while live, the manager pings the winner; if it dies, it either fails over to the next-ranked stream or returns to the mix.
- Disconnect — the WHIP pipe stream is torn down when its remote track ends
(the WHIP server re-registers
pipe:on each new connect).
Restart behaviour
- Live state is in-memory (
_active), so restartingserver.jsdrops the current takeover and the channel falls back to the mix. This is normal. - The stream registry itself is persisted in
station-config.json, so the ranked pool survives.
Endpoints it powers
| Route | Purpose |
|---|---|
GET /api/station/live | Status snapshot (registry + active) |
POST /api/station/live/register | Add/refresh a stream |
POST /api/station/live/remove | Remove by id |
POST /api/station/live/vote | Vote {id,delta} |
POST /api/station/live/start | Go live (top-ranked, or {id}) |
POST /api/station/live/stop | Stop the takeover |
WHIP integration
The mobile WebRTC path (go-live.html → whip_server.py) feeds this manager as
a pipe: stream (url="pipe:/home/sms/radio/whep_live.wav", ingest="webrtc").
The WHIP server re-registers on every new audio track, and the manager's dedupe
keeps exactly one pipe entry. See Live ingest.
See also
- Station control — the API surface around this.
- Liquidsoap — the
channel_liveharbor + switch. - MCP radio — how an agent drives it.