Files
Media-Server-Setup/live/src/api.js
Brandyn / Techy 9bdf9f1947 WebRTC audio mirror: AAC cannot cross WebRTC, so transcode audio to Opus into <path>.webrtc
bin/mtx-on-ready.sh (runOnReady inside the mediamtx container) posts the is-live notice and, for sources whose
audio is not Opus, runs ffmpeg: video copy, audio -> Opus, published over MediaMTX's loopback RTSP to
<user>/<Title>.webrtc (record: no). dumpd accepts .webrtc publishes only from 127.0.0.1. The portal hides
mirror paths, plays the mirror over WHEP (HLS stays on the source), holds an AAC source for 15 s while its
mirror comes up, and corrects the viewer count. Verified headless with a mocked path list.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 13:58:15 -06:00

39 lines
2.0 KiB
JavaScript

// 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`