Live portal: React + dockview multi-stream viewer behind Authentik; MediaMTX HLS/WebRTC/API re-enabled; is-live Discord hook

live/: every active stream opens as a dockable window (WHEP, HLS fallback), audio follows the
focused window, per-window mute/volume, pinning. Caddy live.HOST block with forward-auth proxies
/api /webrtc /hls to MediaMTX on loopback and serves /srv/live. WebRTC media on 8189 (firewall).
configure.py asks the live hostname and an optional Discord webhook; MediaMTX -ffmpeg image runs
the runOnReady hook. deployLive.sh builds and ships dist/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-30 23:01:11 -05:00
parent 3d42271ae2
commit 463d9b2385
22 changed files with 2398 additions and 28 deletions

23
live/src/api.js Normal file
View File

@@ -0,0 +1,23 @@
// MediaMTX control API, proxied by Caddy at /api (-> 127.0.0.1:9997). Returns active streams.
export async function fetchStreams() {
const r = await fetch('/api/v3/paths/list?itemsPerPage=200', { credentials: 'same-origin' })
if (!r.ok) throw new Error(`api ${r.status}`)
const data = await r.json()
return (data.items || [])
.filter(p => p.ready && (p.tracks || []).length > 0)
.map(p => {
const [user, ...rest] = p.name.split('/')
return {
id: p.name, // e.g. techy/VanguardTraining
user,
title: rest.join('/') || p.name,
viewers: (p.readers || []).length,
since: p.readyTime,
tracks: p.tracks || [],
}
})
.sort((a, b) => (a.since || '').localeCompare(b.since || ''))
}
export const whepUrl = id => `/webrtc/${id}/whep`
export const hlsUrl = id => `/hls/${id}/index.m3u8`