Installing the Web Relay
shiloh-web-relay is the browser gateway. It subscribes to shiloh-mixer as three UDP relay clients (one for each feed: main, monitor, cue), encodes each feed to Opus, and serves them over WebRTC using the WHEP protocol. Any browser that supports WebRTC can tune in with sub-500 ms latency — no plugin required.
Architecture
shiloh-mixer ──UDP 5005──▶ shiloh-web-relay (relay_sub)
│ Opus encoder (20 ms frames, 128 kbps VBR)
▼
axum HTTP server :8890
│ POST /whep/main → WebRTC PeerConnection
│ POST /whep/monitor
└ POST /whep/cue
▼
Browser (WHEP client, <audio> element)
Prerequisites
- Linux (same host as
shiloh-mixer, or a host with UDP access to port 5005 on the mixer) - Rust toolchain (
rustup) - The Opus shared library:
sudo apt install libopus-dev
1. Build the binary
cd ~/shiloh-broadcaster
cargo build --release -p shiloh-web-relay
Optionally install to ~/.local/bin:
ln -sfn "$(pwd)/target/release/shiloh-web-relay" ~/.local/bin/shiloh-web-relay
2. Run
shiloh-web-relay \
--mixer 127.0.0.1:5005 \
--control 127.0.0.1:19997 \
--http 0.0.0.0:8890
| Flag | Default | Description |
|---|---|---|
--mixer HOST:PORT |
127.0.0.1:5005 |
Mixer relay UDP address |
--control HOST:PORT |
127.0.0.1:19997 |
Mixer control plane (assigns feeds) |
--http ADDR:PORT |
127.0.0.1:8890 |
HTTP listen address for WHEP |
--name-prefix PREFIX |
web-relay |
Prefix for the three relay client names (web-relay-main, web-relay-monitor, web-relay-cue) |
--announce-ip IP[,IP] |
(auto) | Public IP(s) for ICE candidates — required behind NAT |
--ice-network-types udp4 |
(all) | Restrict ICE transport types; udp4 disables IPv6 and TCP candidates |
On startup you should see:
INFO shiloh_web_relay: shiloh-web-relay: mixer=127.0.0.1:5005 control=127.0.0.1:19997 http=0.0.0.0:8890 prefix=web-relay
INFO shiloh_web_relay::relay_sub: registered "web-relay-main" → session=0xaabbccdd
INFO shiloh_web_relay::relay_sub: registered "web-relay-monitor" → session=0xaabbccde
INFO shiloh_web_relay::relay_sub: registered "web-relay-cue" → session=0xaabbccdf
INFO shiloh_web_relay: WHEP server listening on http://0.0.0.0:8890
3. Test in a browser
Open http://server-ip:8890/ in a browser. The built-in index page lets you pick a feed (main / monitor / cue) and start listening. WebRTC setup takes 1–3 seconds.
Alternatively, hit the WHEP endpoint directly:
# POST an SDP offer to start a session (WHEP protocol)
curl -s -X POST http://server-ip:8890/whep/main \
-H "Content-Type: application/sdp" \
--data-binary @offer.sdp
4. Reverse proxy with nginx
To serve the WHEP endpoint under your main domain (e.g. https://listen.example.com/webrtc/):
server {
listen 443 ssl;
server_name listen.example.com;
# ... ssl_certificate, ssl_certificate_key, etc.
location /webrtc/ {
proxy_pass http://127.0.0.1:8890/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WHEP uses chunked responses; disable buffering.
proxy_buffering off;
proxy_read_timeout 60s;
}
}
NOTE: If the web relay is behind NAT, pass
--announce-ip <public-ip>so the ICE candidates in the SDP answer contain a reachable address. Without this, browsers outside the LAN cannot complete the WebRTC handshake.
5. systemd user unit
Create ~/.config/systemd/user/shiloh-web-relay.service:
[Unit]
Description=shiloh-web-relay — WebRTC/WHEP browser egress
After=shiloh-mixer.service network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=%h/.local/bin/shiloh-web-relay \
--mixer 127.0.0.1:5005 \
--control 127.0.0.1:19997 \
--http 0.0.0.0:8890
Restart=on-failure
RestartSec=5
Environment=RUST_LOG=info
[Install]
WantedBy=default.target
systemctl --user daemon-reload
systemctl --user enable shiloh-web-relay.service
systemctl --user start shiloh-web-relay.service
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Browser says “ICE failed” | Behind NAT without --announce-ip; or firewall blocking UDP |
| No audio after connection | Mixer not streaming to this relay yet (check feed assignment in mixer_web UI) |
bind 0.0.0.0:8890 fails |
Port already in use; check `ss -tlnp |
| Three clients registering but mixer rejects | Mixer [ingest] and relay allow-lists are separate — relay clients do not need [[ingest.sender]] entries |