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:
+59
-17
@@ -50,7 +50,7 @@ CONFIG = {
|
||||
"http_port": 48080,
|
||||
"firewall_rule_name": "GameStream-UDP-48189",
|
||||
"public_url": "https://stream.hetherman.cloud",
|
||||
"api_url": "http://127.0.0.1:49997",
|
||||
"api_url": "http://127.0.0.1:19997",
|
||||
}
|
||||
|
||||
|
||||
@@ -158,10 +158,15 @@ def start_mediamtx() -> None:
|
||||
creationflags = 0
|
||||
if os.name == "nt":
|
||||
creationflags = getattr(subprocess, "CREATE_NO_WINDOW", 0)
|
||||
log_path = os.path.join(os.path.dirname(binary), "mediamtx.log")
|
||||
log(f"MediaMTX binary: {binary!r}")
|
||||
log(f"MediaMTX config: {config!r}")
|
||||
log(f"MediaMTX log: {log_path}")
|
||||
mediamtx_log = open(log_path, "w")
|
||||
STATE.mediamtx_proc = subprocess.Popen(
|
||||
[binary, config],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
stdout=mediamtx_log,
|
||||
stderr=mediamtx_log,
|
||||
creationflags=creationflags,
|
||||
)
|
||||
log(f"MediaMTX started (pid={STATE.mediamtx_proc.pid})")
|
||||
@@ -197,7 +202,38 @@ def stop_mediamtx() -> None:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class _QuietHandler(http.server.SimpleHTTPRequestHandler):
|
||||
"""SimpleHTTPRequestHandler that logs to the OBS log instead of stderr."""
|
||||
"""SimpleHTTPRequestHandler that serves static files and proxies /status."""
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == "/status":
|
||||
# Proxy the MediaMTX path status so the browser doesn't need to
|
||||
# hit /v3/ through NPM (where auth_request blocks it).
|
||||
api_url = f"{CONFIG['api_url']}/v3/paths/get/game-opus"
|
||||
try:
|
||||
with urllib.request.urlopen(api_url, timeout=2) as resp:
|
||||
body = resp.read()
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
except urllib.error.HTTPError as exc:
|
||||
body = b'{"ready":false}'
|
||||
self.send_response(exc.code)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
except Exception:
|
||||
body = b'{"ready":false}'
|
||||
self.send_response(503)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
return
|
||||
super().do_GET()
|
||||
|
||||
def log_message(self, format, *args): # noqa: A002 - match signature
|
||||
# Keep OBS script log clean; only log errors.
|
||||
@@ -273,7 +309,7 @@ def poll_status() -> None:
|
||||
if STATE.mediamtx_proc is None:
|
||||
return
|
||||
|
||||
url = f"{CONFIG['api_url']}/v3/paths/get/game"
|
||||
url = f"{CONFIG['api_url']}/v3/paths/get/game-opus"
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=2) as resp:
|
||||
import json
|
||||
@@ -304,27 +340,28 @@ def poll_status() -> None:
|
||||
|
||||
def _on_frontend_event(event):
|
||||
if event == obs.OBS_FRONTEND_EVENT_STREAMING_STARTED:
|
||||
log("OBS streaming started -> bringing up game-stream-app")
|
||||
# MediaMTX is already running (started on script load / settings save).
|
||||
# Just enable the firewall, start the status poller, and log the URL.
|
||||
enable_firewall_rule()
|
||||
start_mediamtx()
|
||||
start_http_server()
|
||||
if not STATE.poll_timer_registered:
|
||||
obs.timer_add(poll_status, 5000)
|
||||
STATE.poll_timer_registered = True
|
||||
log(f"Viewers can watch at: {CONFIG['public_url']}")
|
||||
elif event in (
|
||||
obs.OBS_FRONTEND_EVENT_STREAMING_STOPPED,
|
||||
obs.OBS_FRONTEND_EVENT_EXIT,
|
||||
):
|
||||
log("OBS streaming stopped -> tearing down game-stream-app")
|
||||
log(f"OBS streaming started -> viewers can watch at: {CONFIG['public_url']}")
|
||||
elif event == obs.OBS_FRONTEND_EVENT_STREAMING_STOPPED:
|
||||
log("OBS streaming stopped -> disabling firewall rule")
|
||||
if STATE.poll_timer_registered:
|
||||
obs.timer_remove(poll_status)
|
||||
STATE.poll_timer_registered = False
|
||||
disable_firewall_rule()
|
||||
STATE.last_status = "offline"
|
||||
STATE.last_viewers = 0
|
||||
elif event == obs.OBS_FRONTEND_EVENT_EXIT:
|
||||
if STATE.poll_timer_registered:
|
||||
obs.timer_remove(poll_status)
|
||||
STATE.poll_timer_registered = False
|
||||
stop_mediamtx()
|
||||
stop_http_server()
|
||||
disable_firewall_rule()
|
||||
STATE.last_status = "offline"
|
||||
STATE.last_viewers = 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -409,7 +446,7 @@ def script_defaults(settings):
|
||||
settings, "public_url", "https://stream.hetherman.cloud",
|
||||
)
|
||||
obs.obs_data_set_default_string(
|
||||
settings, "api_url", "http://127.0.0.1:49997",
|
||||
settings, "api_url", "http://127.0.0.1:19997",
|
||||
)
|
||||
|
||||
|
||||
@@ -428,6 +465,11 @@ def script_update(settings):
|
||||
def script_load(settings):
|
||||
script_update(settings)
|
||||
obs.obs_frontend_add_event_callback(_on_frontend_event)
|
||||
# Start MediaMTX and HTTP server immediately so they are ready before
|
||||
# OBS attempts the WHIP connection. OBS fires STREAMING_STARTED only
|
||||
# after a successful connect, so MediaMTX must be up first.
|
||||
start_mediamtx()
|
||||
start_http_server()
|
||||
log("game_stream.py loaded")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user