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>
39 lines
2.0 KiB
JavaScript
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`
|