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>
This commit is contained in:
@@ -418,11 +418,17 @@ class H(BaseHTTPRequestHandler):
|
||||
return self._json(200, {"ok": True}) # viewers are gated by Caddy + Authentik
|
||||
if action != "publish":
|
||||
return self._json(401, {"error": f"action {action} not allowed"})
|
||||
path = (req.get("path") or "").lower()
|
||||
if path.endswith(".webrtc"):
|
||||
# audio mirrors (<user>/<Title>.webrtc) are published by the hook inside the mediamtx container only
|
||||
if req.get("ip") in ("127.0.0.1", "::1") and re.fullmatch(r"[a-z0-9_.-]+/[a-z0-9_.-]+\.webrtc", path):
|
||||
return self._json(200, {"ok": True})
|
||||
print(f"{datetime.now(TZ):%F %T} mtx-auth DENY publish path={path} ip={req.get('ip')} (mirror paths are internal)", flush=True)
|
||||
return self._json(401, {"error": ".webrtc paths are reserved"})
|
||||
cred = req.get("password") or req.get("token") or ""
|
||||
if cred.lower().startswith("bearer "): cred = cred[7:]
|
||||
if ":" in cred and "." in cred.split(":", 1)[1]: cred = cred.split(":", 1)[1] # "user:token" form
|
||||
user = verify_token(cred)
|
||||
path = (req.get("path") or "").lower()
|
||||
if not user:
|
||||
print(f"{datetime.now(TZ):%F %T} mtx-auth DENY publish path={path} ip={req.get('ip')} (bad/expired token)", flush=True)
|
||||
return self._json(401, {"error": "invalid or expired stream token"})
|
||||
|
||||
36
bin/mtx-on-ready.sh
Executable file
36
bin/mtx-on-ready.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
# MediaMTX runOnReady hook (runs INSIDE the mediamtx container; busybox sh + wget + ffmpeg from the -ffmpeg image).
|
||||
# Env from MediaMTX: MTX_PATH (user/Title). From compose: LIVE_WEBHOOK_URL, LIVE_HOST.
|
||||
# 1. optional Discord "is live" notice
|
||||
# 2. WebRTC audio mirror: browsers cannot receive AAC over WebRTC (MediaMTX forwards Opus/G711/G722/LPCM only), and
|
||||
# OBS sends AAC. For every stream whose audio is not already Opus, copy the video and transcode the audio to Opus
|
||||
# into the sibling path "<user>/<Title>.webrtc". The live portal plays that path over WebRTC; recordings, HLS and
|
||||
# the Discord notice stay on the original. Mirrors are published from 127.0.0.1 inside this container only, which
|
||||
# is the only publisher dumpd accepts for ".webrtc" paths. Audio-only transcode: a few % of one core per stream.
|
||||
# MediaMTX kills this process when the source path goes away.
|
||||
case "$MTX_PATH" in *.webrtc) exit 0 ;; esac
|
||||
u="${MTX_PATH%%/*}"; t="${MTX_PATH#*/}"
|
||||
if [ -n "$LIVE_WEBHOOK_URL" ]; then
|
||||
wget -q -O /dev/null --header="Content-Type: application/json" \
|
||||
--post-data="{\"content\":\"🔴 **$u** is live: $t — https://$LIVE_HOST/\"}" "$LIVE_WEBHOOK_URL" 2>/dev/null &
|
||||
fi
|
||||
# which audio does the source carry? (API on loopback; "Opus" needs no mirror, no audio needs no mirror)
|
||||
tracks=""
|
||||
for i in 1 2 3 4 5; do
|
||||
tracks=$(wget -q -O - "http://127.0.0.1:9997/v3/paths/get/$MTX_PATH" 2>/dev/null) && [ -n "$tracks" ] && break
|
||||
sleep 1
|
||||
done
|
||||
case "$tracks" in
|
||||
*Opus*) exit 0 ;;
|
||||
*"MPEG-4 Audio"*|*"MPEG-1 Audio"*|*G711*|*G722*|*LPCM*|*AC-3*) ;;
|
||||
*) exit 0 ;; # no (recognised) audio: nothing to mirror
|
||||
esac
|
||||
echo "mirror: $MTX_PATH -> $MTX_PATH.webrtc (audio -> Opus)"
|
||||
n=0
|
||||
while [ $n -lt 10 ]; do # retry: the source may not be readable for the first second
|
||||
ffmpeg -nostdin -hide_banner -loglevel warning -fflags nobuffer -flags low_delay \
|
||||
-rtsp_transport tcp -i "rtsp://127.0.0.1:8554/$MTX_PATH" \
|
||||
-map 0:v:0 -map 0:a:0 -c:v copy -c:a libopus -b:a 128k -ar 48000 -ac 2 -application lowdelay -frame_duration 20 \
|
||||
-f rtsp -rtsp_transport tcp "rtsp://127.0.0.1:8554/$MTX_PATH.webrtc"
|
||||
n=$((n+1)); sleep 2
|
||||
done
|
||||
Reference in New Issue
Block a user