Radio Engine (radio-engine.js)
radio-engine.js is the bridge between the control plane and the broadcast
node. It owns the channel abstraction, the sync of playlists/media, and the
realtime now-playing telemetry that feeds every frontend.
Channel abstraction
The engine exposes radio channels (e.g. main). Each channel has:
- a now-playing item (
radio.nowPlaying) - a queue
- a channel id and its own Socket.io namespace
radio.nowPlaying is the canonical state emitted in status events (see
Socket events).
Sync to production
Since 2026-08-07 the engine uploads over HTTP pull, not scp. The full
mechanism (endpoints, SSH_OPTS, fingerprints, verification) is in
Media sync; the short version:
remoteWrite()opens one ssh per file to run a tiny control command on the remote:mkdir -p … && curl -sf -o '<tmp>' "http://10.40.3.174:5000/api/sync/media?rel=…&token=…" && cat '<tmp>' > '<final>' && rm -f '<tmp>' && stat -c %s.- The remote pulls the bytes over plain HTTP (token + path-guarded), writes the
tmp in place (
cat tmp > final) so Liquidsoap'swatchreload fires, and returns the final size. - 5 retries with exponential backoff + a 190s watchdog SIGKILL.
SSH_OPTS=-o BatchMode=yes -o PreferredAuthentications=publickeyon every ssh (includingensureProcess/remoteExec) so a keyless SYSTEM-session engine fails fast instead of hanging on a password prompt.
updateRemoteStream is serialized via a _syncInFlight lock — a heartbeat
and a refresh-resync can no longer double-push/rewind the remote.
Content-fingerprint manifest
remoteSynced (persisted to E:\radionew\logs\remote_synced.json) stores
path#size#mtimeMs fingerprints, so re-fetched/retagged files re-upload and a
restart never re-pushes the whole 120-file rotation.
Rotation verification (verifyRemoteRotation)
After each playlist push the engine size-checks every remote file
(stat -c %s | grep -qx <localSize>), chunked 20 paths per ssh to stay under
the Windows 32 KB command-line limit. Boot logs [Sync] Rotation verified on remote (N files present, size-checked) with zero MISS when healthy.
Playlist push (finalizeSync)
The M3U is pushed only when its content actually changed:
finalizeSyncreads the local M3U and compares it tolastPushedM3uContent. If unchanged, it skips the upload entirely and just verifies Liquidsoap is alive (ensureProcess). This prevents a reload churn: Liquidsoap watches the file, and a needless rewrite would reload the rotation and rewind the stream onto tracks that just played.- When it does push, it uploads in place. M3U files are written with
dd if='<tmp>' of='<final>' conv=notrunc bs=1M— a single write syscall (oneIN_MODIFY), whereas media files usecat tmp > final. Because the m3u is padded to a fixed 32768 bytes by the scheduler (FIXED_M3U_SIZE), dd-notrunc fully overwrites it with no stale tail, and Liquidsoap reloads exactly once per push instead of twice (see Double-reload fix). - An empty M3U is never pushed — an empty overwrite makes Liquidsoap emit
Fetch failed: emptyand go silent; the last good rundown is preserved.
Remote configuration
| Constant | Value |
|---|---|
UBUNTU_IP | 10.10.8.230 |
UBUNTU_USER | sms |
REMOTE_PATH | /home/sms/radio |
ICECAST_PASSWORD | Icecast source password (installed at deployment) |
| Local stream port | 8005 (Icecast source port) |
Failover behavior
- If a track fails mid-play, the engine emits a fresh
status(server keeps listeners updated) and moves to the next queued item. - Sync failures are retried, then logged — they never crash the scheduler loop.
- The listener-count source is MediaMTX HLS, not Icecast:
updateStats()polls the read-only listener proxy athttp://10.10.8.230:19353/v3/paths/listevery 5 s →emitStats(readers.length, 128, 'mediamtx'). Icecast'scountIcecast()is only a fallback when MediaMTX is unreachable (source=mediamtx|icecastinstats_update).
How it connects to the server
server.js instantiates the engine and passes it the Socket.io server so the
engine can emit channel events. This is why every frontend can display
now-playing without polling.
See also
- Server overview — how the engine plugs into Express/Socket.io.
- Media sync — the full sync mechanism.