// MediaMTX control API, proxied by Caddy at /api (-> 127.0.0.1:9997). Returns active streams. const VIDEO = new Set(['H264', 'H265', 'VP8', 'VP9', 'AV1', 'MJPEG', 'M-JPEG', 'MPEG-4 Video', 'MPEG-1 Video']) const firstSeen = new Map() // path -> first time we saw it ready (mirror grace period) const MIRROR_WAIT_MS = 15000 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() const ready = (data.items || []).filter(p => p.ready && (p.tracks || []).length > 0) const byName = new Map(ready.map(p => [p.name, p])) const now = Date.now() for (const p of ready) if (!firstSeen.has(p.name)) firstSeen.set(p.name, now) for (const k of [...firstSeen.keys()]) if (!byName.has(k)) firstSeen.delete(k) const out = [] for (const p of ready) { if (p.name.endsWith('.webrtc')) continue // audio mirror: played through its source's panel, never its own const tracks = p.tracks || [] const audio = tracks.filter(t => !VIDEO.has(t)) const needsMirror = audio.length > 0 && !audio.includes('Opus') // AAC etc. cannot cross WebRTC const mirror = byName.get(p.name + '.webrtc') if (needsMirror && !mirror && now - firstSeen.get(p.name) < MIRROR_WAIT_MS) continue // give the mirror a moment to come up const [user, ...rest] = p.name.split('/') out.push({ id: p.name, // e.g. techy/VanguardTraining user, title: rest.join('/') || p.name, whep: mirror ? mirror.name : p.name, // WebRTC plays the Opus mirror when there is one; HLS always the source viewers: (p.readers || []).length + (mirror ? (mirror.readers || []).length - 1 : 0), // the mirror's ffmpeg is one reader since: p.readyTime, tracks, }) } return out.sort((a, b) => (a.since || '').localeCompare(b.since || '')) } export const whepUrl = id => `/webrtc/${id}/whep` export const hlsUrl = id => `/hls/${id}/index.m3u8`