Recipe: run a 24/7 radio station with zero transcode
A looping playlist pushed as RTMP, remuxed straight to HLS with no re-encode, playing around the clock. The passthrough path, the trade-offs it makes, and the autoDJ loop that feeds it.
Why a passthrough radio
A 24/7 station is the one workload where you do not want the server transcoding: the audio (and a static cover image) is already encoded, the CPU should sit near idle, and it must never stop. StreamHub's RTMP passthrough remuxes your feed straight to HLS with ffmpeg -c copy — no re-encode, minimal CPU — so one small box can carry a station that runs forever.
Architecture in words
Passthrough is a distinct path that bypasses LiveKit: an incoming RTMP (H.264 video + AAC audio) is remuxed to HLS segments on disk and served publicly. There is no WebRTC room, no adaptive ladder, no server recording — you trade all of that for near-zero CPU and a rock-steady HLS output. The source is an "autoDJ": a looping ffmpeg push of your playlist plus a station image, which is a technique you run, not a StreamHub feature.
1. Enable passthrough on the server
Passthrough is off by default. Set two env vars and restart the core:
STREAMHUB_RTMP_PASSTHROUGH_ENABLE=on
STREAMHUB_RTMP_PASSTHROUGH_PORT=1937 # NOT LiveKit's 1935
2. Put the app in passthrough mode
Apply the preset (it sets rtmp.passthrough on and turns transcode, adaptive and recording off), or patch the config directly:
export BASE="https://your-server.example.com/api/v1"
export TOKEN="sk_...."
curl -s -X POST $BASE/apps/radio/presets/rtmp-passthrough/apply -H "Authorization: Bearer $TOKEN"
# or: curl -s -X PATCH $BASE/apps/radio/config -H "Authorization: Bearer $TOKEN" \
# -H 'Content-Type: application/json' -d '{"rtmpPassthrough": true}'
3. Create the passthrough ingress
curl -s -X POST $BASE/apps/radio/ingress -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"inputType":"rtmp","room":"fm1","mode":"passthrough"}'
The response gives you a passthrough push URL (rtmp://your-server.example.com:1937/radio/<key>) and the public playback URLs:
hls_url: /hls/radio/fm1/index.m3u8
player_url: /play/radio/fm1
4. Feed it with an autoDJ loop
Loop your playlist forever with ffmpeg, pairing the audio with a still cover image so the output is a valid H.264/AAC stream the remux can copy:
ffmpeg -re -stream_loop -1 -i playlist.mp3 \
-loop 1 -i cover.png \
-c:v libx264 -tune stillimage -pix_fmt yuv420p -r 2 \
-c:a aac -b:a 128k -shortest -f flv \
rtmp://your-server.example.com:1937/radio/<key>
OBS with a media source set to loop works just as well. If the source drops, the remux manager re-arms and a looping source reconnects on its own.
What you should see
The station plays at /hls/radio/fm1/index.m3u8 with roughly two segments of latency, CPU stays near idle because nothing re-encodes on the server, and the stream keeps running as long as the loop pushes.
Troubleshooting
- 400 on ingress create — passthrough rejects transcoding, backup keys, audio-only, WHIP and URL inputs; it must be a plain RTMP (or SRT) push in
mode: passthrough. - 503 on ingress create — the server feature is off; set
STREAMHUB_RTMP_PASSTHROUGH_ENABLE=onand restart. - No playback — the source must already be H.264/AAC (there is no transcode safety net); the cover-image ffmpeg command above guarantees that.
- You need WebRTC, chat or recording — those do not exist on the passthrough path. If you want them plus low CPU, use the LiveKit path with
transcoding.enabled: falseinstead ofrtmp.passthrough.
Interactive audio instead?
If you actually want a live, talking radio (a host on a mic, sub-second, with a public "listen" embed), that is the separate radio plugin — WebRTC audio with a public listen-token. Passthrough is for a continuous file-fed broadcast; the radio plugin is for a live show.
Closing
A passthrough station is about as cheap as streaming gets, and it is all yours. If you want scheduling, ad insertion, or a branded now-playing page wired into this, our team builds those around the open-source core as a consulting engagement.