Fix audio, routing, auth, and stream lifecycle

- Switch OBS output to RTMP; add FFmpeg AAC->Opus transcoding via MediaMTX
  runOnReady so WebRTC can carry audio (WebRTC requires Opus, not AAC)
- Enable RTSP on localhost so FFmpeg reads game path without publisher conflict;
  viewers connect to game-opus path (H264+Opus)
- Fix WHEP/HLS path prefix stripping in NPM advanced config; move all custom
  locations (/whep, /hls, /v3) out of NPM GUI and into advanced conf so
  trailing-slash proxy_pass correctly strips prefixes before hitting MediaMTX
- Fix MediaMTX API port 49997->19997 (49997 was in Windows ephemeral range)
- Add /status proxy endpoint to OBS HTTP server so frontend can poll stream
  readiness without hitting /v3/ through NPM where auth_request blocked it
- Fix authInternalUsers: split publish (localhost only) from read (any IP)
  so WHEP viewers are not challenged with Basic Auth by MediaMTX
- Remove muted attribute from video element; show unmute/play button on
  autoplay block so viewers get audio after one click
- Fix webrtcAdditionalHosts to include LAN IP 192.168.50.254
- Fix hlsAllowOrigin->hlsAllowOrigins deprecation warning
- Move MediaMTX/HTTP server startup to script_load (not streaming started)
  so MediaMTX is ready before OBS attempts RTMP connection
- Log MediaMTX output to bin/mediamtx.log for easier debugging

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 03:42:38 -04:00
parent c23e8799fe
commit 180e95f74d
9 changed files with 160 additions and 68 deletions
+15 -8
View File
@@ -10,8 +10,10 @@
*/
(function () {
const PATH_NAME = 'game';
const API_URL = `/v3/paths/get/${PATH_NAME}`;
// /status is served by the OBS Python HTTP server, which proxies the
// MediaMTX API internally. This avoids hitting /v3/ through NPM where
// the server-level auth_request directive blocks it.
const API_URL = '/status';
const POLL_INTERVAL_MS = 5000;
const els = {
@@ -105,11 +107,11 @@
}
}
// ---- Unmute button --------------------------------------------------
// ---- Unmute / play button -------------------------------------------
//
// Browsers require a user gesture to play audio. The <video> element
// starts muted so autoplay works; we surface a button to unmute once
// playback begins.
// Browsers block autoplay with audio until a user gesture occurs.
// When the video fails to autoplay (or is muted by the browser), we
// surface a "Click to play" button so the user can unblock it manually.
els.unmuteBtn.addEventListener('click', async () => {
try {
@@ -117,12 +119,17 @@
await els.video.play();
els.unmuteBtn.hidden = true;
} catch (err) {
console.warn('Unmute failed:', err);
console.warn('Unmute/play failed:', err);
}
});
function maybeShowUnmute() {
els.unmuteBtn.hidden = !els.video.muted;
// Show the button if the video is muted OR paused (autoplay blocked).
const needsGesture = els.video.muted || els.video.paused;
els.unmuteBtn.hidden = !needsGesture;
if (needsGesture) {
els.unmuteBtn.textContent = els.video.muted ? 'Click to unmute' : 'Click to play';
}
}
// ---- StreamPlayer event wiring --------------------------------------
+2 -2
View File
@@ -16,8 +16,8 @@
*/
(function () {
const WHEP_URL = '/whep/game/whep';
const HLS_URL = '/hls/game/index.m3u8';
const WHEP_URL = '/whep/game-opus/whep';
const HLS_URL = '/hls/game-opus/index.m3u8';
const HLS_JS_CDN = 'https://cdn.jsdelivr.net/npm/hls.js@1.5.17/dist/hls.min.js';
const WHEP_TIMEOUT_MS = 10000;
const RECONNECT_DELAY_MS = 3000;