Update all docs to reflect current working configuration

- README: update architecture diagram for RTMP+FFmpeg pipeline, add FFmpeg
  install step, fix path descriptions
- obs-setup: switch from WHIP to RTMP output, add FFmpeg prerequisite, fix
  script log messages (MediaMTX starts on load not streaming start), add
  Python setup note, update troubleshooting for game-opus path and audio
- npm-setup: remove Custom Locations GUI instructions (must be empty - all
  locations defined in Advanced tab only), update verify steps to game-opus
  paths, add troubleshooting for WHEP 400/401 causes
- authentik-setup: add section 6 covering both manual account creation and
  self-service enrollment via invite link; clarify User Write stage group
  field is what triggers auto-add (not the invitation form)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 03:46:13 -04:00
parent 180e95f74d
commit 0dff4eeee3
4 changed files with 158 additions and 86 deletions
+22 -12
View File
@@ -12,13 +12,21 @@ Authentik authentication.
## How it works ## How it works
``` ```
OBS Studio (NVENC, WHIP out) OBS Studio (NVENC H.264, RTMP out)
-> MediaMTX (localhost) ---> WHEP / HLS / API -> MediaMTX (localhost, RTMP ingest)
-> Frontend HTTP server (localhost:8080) -> FFmpeg (AAC -> Opus transcode, internal RTSP)
-> game-opus path (H.264 + Opus)
-> WHEP / HLS for viewers
-> Frontend HTTP server (localhost:48080)
-> NPM (TLS, Authentik forward auth, reverse proxy) -> NPM (TLS, Authentik forward auth, reverse proxy)
-> Friend's browser -> Friend's browser
``` ```
OBS outputs RTMP to MediaMTX on localhost. MediaMTX spawns FFmpeg via
`runOnReady` to transcode audio from AAC to Opus (required for WebRTC) and
re-publish to the `game-opus` path. Viewers connect to `game-opus` via WHEP
(WebRTC) or HLS.
Everything on the gaming PC (MediaMTX, HTTP server, Windows Firewall rule for Everything on the gaming PC (MediaMTX, HTTP server, Windows Firewall rule for
the WebRTC UDP port) is spawned and torn down by an OBS Python script - the WebRTC UDP port) is spawned and torn down by an OBS Python script -
`obs-script/game_stream.py`. You just click **Start Streaming** in OBS and the `obs-script/game_stream.py`. You just click **Start Streaming** in OBS and the
@@ -28,7 +36,7 @@ whole pipeline comes up; click **Stop Streaming** and it all goes away.
| Path | Purpose | | Path | Purpose |
|-------------------------------|-------------------------------------------------------------| |-------------------------------|-------------------------------------------------------------|
| `config/mediamtx.yml` | MediaMTX configuration (WHIP in, WHEP/HLS out, locked-down) | | `config/mediamtx.yml` | MediaMTX configuration (RTMP in, WHEP/HLS out, FFmpeg transcode) |
| `config/npm-advanced.conf` | Authentik forward-auth snippet for the NPM Advanced tab | | `config/npm-advanced.conf` | Authentik forward-auth snippet for the NPM Advanced tab |
| `obs-script/game_stream.py` | OBS script: lifecycle, HTTP server, firewall toggle | | `obs-script/game_stream.py` | OBS script: lifecycle, HTTP server, firewall toggle |
| `frontend/index.html` | Viewer page | | `frontend/index.html` | Viewer page |
@@ -36,27 +44,29 @@ whole pipeline comes up; click **Stop Streaming** and it all goes away.
| `frontend/js/app.js` | Status polling and DOM glue | | `frontend/js/app.js` | Status polling and DOM glue |
| `frontend/css/style.css` | Dark theme | | `frontend/css/style.css` | Dark theme |
| `scripts/install.ps1` | Downloads MediaMTX, creates the Windows Firewall rule | | `scripts/install.ps1` | Downloads MediaMTX, creates the Windows Firewall rule |
| `docs/authentik-setup.md` | Authentik proxy provider + group configuration | | `docs/authentik-setup.md` | Authentik proxy provider, group, and invite configuration |
| `docs/npm-setup.md` | NPM proxy host + stream (UDP) configuration | | `docs/npm-setup.md` | NPM proxy host + stream (UDP) configuration |
| `docs/obs-setup.md` | OBS encoder + WHIP output settings | | `docs/obs-setup.md` | OBS encoder + RTMP output settings |
## Setup at a glance ## Setup at a glance
1. **Clone** this repo onto the Windows gaming PC. 1. **Clone** this repo onto the Windows gaming PC.
2. **Install MediaMTX and the firewall rule:** open an elevated PowerShell in 2. **Install FFmpeg:** `winget install Gyan.FFmpeg` in an elevated PowerShell,
then open a new terminal to verify: `ffmpeg -version`.
3. **Install MediaMTX and the firewall rule:** open an elevated PowerShell in
the repo root and run `.\scripts\install.ps1`. the repo root and run `.\scripts\install.ps1`.
3. **Configure Authentik** - see `docs/authentik-setup.md`. 4. **Configure Authentik** - see `docs/authentik-setup.md`.
4. **Configure NPM** - see `docs/npm-setup.md`. 5. **Configure NPM** - see `docs/npm-setup.md`.
5. **Configure OBS** - see `docs/obs-setup.md`, then add 6. **Configure OBS** - see `docs/obs-setup.md`, then add
`obs-script/game_stream.py` via Tools -> Scripts. `obs-script/game_stream.py` via Tools -> Scripts.
6. **Click Start Streaming in OBS.** Friends can now open 7. **Click Start Streaming in OBS.** Friends can now open
`https://stream.hetherman.cloud`, log in with Authentik, and watch. `https://stream.hetherman.cloud`, log in with Authentik, and watch.
## Security posture ## Security posture
- TLS terminates at NPM with Let's Encrypt. - TLS terminates at NPM with Let's Encrypt.
- Every request is gated by Authentik forward auth before it reaches the - Every request is gated by Authentik forward auth before it reaches the
frontend, WHEP signaling, HLS, or the MediaMTX API. frontend, WHEP signaling, or HLS.
- MediaMTX only accepts publishers from `127.0.0.1` - nobody on the public - MediaMTX only accepts publishers from `127.0.0.1` - nobody on the public
internet can hijack the stream. internet can hijack the stream.
- The UDP port used for WebRTC media is opened on the Windows Firewall only - The UDP port used for WebRTC media is opened on the Windows Firewall only
+37 -3
View File
@@ -11,8 +11,6 @@ have admin access.
1. Authentik admin -> **Directory -> Groups -> Create** 1. Authentik admin -> **Directory -> Groups -> Create**
2. Name: `stream-viewers` 2. Name: `stream-viewers`
3. Save. 3. Save.
4. Add each friend's user account to this group. **Do not** add yourself /
your admin account - your access comes from your existing admin group.
## 2. Create a Proxy Provider ## 2. Create a Proxy Provider
@@ -68,7 +66,43 @@ curl -I https://auth.hetherman.cloud/outpost.goauthentik.io/ping
A 200 or 204 means the outpost is up. A 200 or 204 means the outpost is up.
## 6. Verify end-to-end ## 6. Adding friends
You have two options:
### Option A: Create accounts manually (recommended for small groups)
1. **Directory -> Users -> Create**
- Set username, name, email
- Set a temporary password
- Enable "User must change password on next login"
2. **Directory -> Groups -> stream-viewers -> Users tab -> Add** the new user.
3. Send your friend the URL (`https://stream.hetherman.cloud`) and their
temporary credentials. They set their own password on first login.
### Option B: Self-service enrollment via invite link
This requires a working enrollment flow with an Invitation stage. The key
requirement is that the **User Write stage** in the enrollment flow must have
`stream-viewers` set in the **Groups** field - this is what auto-adds new
users to the group. The invitation form itself has no group field.
1. **Flows & Stages -> Stages** - find or create a User Write stage and set
**Groups** to `stream-viewers`.
2. **Flows & Stages -> Flows** - create an enrollment flow with these stages
in order: Invitation stage -> prompt (email/password) -> User Write -> Login.
3. **Flows & Stages -> Invitations -> Create**:
- **Flow**: your enrollment flow
- **Single use**: on (one invite per person)
- Set an expiry date
4. Copy the invite link (shown after saving) and send it to your friend. They
click it, fill in username/password, and are redirected to the stream.
> **Important:** if friends completed enrollment but were not added to
> `stream-viewers` (because the User Write stage was not configured), add them
> manually: **Directory -> Groups -> stream-viewers -> Users tab -> Add**.
## 7. Verify end-to-end
After finishing `docs/npm-setup.md`: After finishing `docs/npm-setup.md`:
+36 -28
View File
@@ -8,7 +8,7 @@ Configures NPM to:
**Stream** (L4 UDP proxy). **Stream** (L4 UDP proxy).
Replace `<PC-LAN-IP>` with the LAN IP of the Windows gaming PC Replace `<PC-LAN-IP>` with the LAN IP of the Windows gaming PC
(e.g., `192.168.50.10`). (e.g., `192.168.50.254`).
## 1. DNS ## 1. DNS
@@ -22,7 +22,7 @@ Make sure your router forwards these to NPM (not to the PC directly):
| Proto | External port | Internal target | | Proto | External port | Internal target |
|-------|--------------|-------------------| |-------|--------------|-------------------|
| TCP | 443 | NPM host, 443 | | TCP | 443 | NPM host, 443 |
| UDP | 48189 | NPM host, 48189 | | UDP | 48189 | NPM host, 48189 |
(TCP 443 is probably already forwarded for your other services; UDP 48189 is (TCP 443 is probably already forwarded for your other services; UDP 48189 is
the new one for this app.) the new one for this app.)
@@ -38,20 +38,15 @@ In NPM, **Hosts -> Proxy Hosts -> Add Proxy Host**.
| Domain Names | `stream.hetherman.cloud` | | Domain Names | `stream.hetherman.cloud` |
| Scheme | `http` | | Scheme | `http` |
| Forward Hostname | `<PC-LAN-IP>` | | Forward Hostname | `<PC-LAN-IP>` |
| Forward Port | `48080` | | Forward Port | `48080` |
| Cache Assets | off | | Cache Assets | off |
| Block Common Exploits | on | | Block Common Exploits | on |
| Websockets Support | **on** (WebRTC signaling works without this, but it costs nothing) | | Websockets Support | **on** |
**Custom locations tab:** add three entries so WHEP, HLS, and the MediaMTX **Custom Locations tab:** leave this **empty**. Do not add any custom location
API are reverse-proxied to the right MediaMTX ports (and inherit the same entries here. The `/whep/`, `/hls/`, and `/v3/` locations are defined in the
forward-auth gating). Advanced tab config below with trailing-slash path stripping. Adding them in
the GUI creates duplicate nginx location blocks that cause routing failures.
| Location | Scheme | Forward Hostname | Forward Port |
|----------|--------|------------------|--------------|
| `/whep` | `http` | `<PC-LAN-IP>` | `48889` |
| `/hls` | `http` | `<PC-LAN-IP>` | `48888` |
| `/v3` | `http` | `<PC-LAN-IP>` | `19997` |
**SSL tab:** **SSL tab:**
@@ -61,8 +56,12 @@ forward-auth gating).
- HSTS Enabled: optional - HSTS Enabled: optional
**Advanced tab:** paste the entire contents of **Advanced tab:** paste the entire contents of
[`config/npm-advanced.conf`](../config/npm-advanced.conf). This installs the [`config/npm-advanced.conf`](../config/npm-advanced.conf). This installs:
Authentik forward-auth subrequest and the sign-in redirect. - Authentik forward-auth subrequest on all requests
- `/whep/` -> MediaMTX WHEP port 48889 (prefix stripped)
- `/hls/` -> MediaMTX HLS port 48888 (prefix stripped)
- `/v3/` -> MediaMTX API port 19997 (prefix stripped, auth bypassed)
- `/outpost.goauthentik.io` -> Authentik internal outpost
Save the proxy host. Wait for the Let's Encrypt certificate to be issued. Save the proxy host. Wait for the Let's Encrypt certificate to be issued.
@@ -72,9 +71,9 @@ In NPM, **Hosts -> Streams -> Add Stream**.
| Field | Value | | Field | Value |
|-------------------|---------------| |-------------------|---------------|
| Incoming Port | `48189` | | Incoming Port | `48189` |
| Forward Host | `<PC-LAN-IP>` | | Forward Host | `<PC-LAN-IP>` |
| Forward Port | `48189` | | Forward Port | `48189` |
| TCP | **off** | | TCP | **off** |
| UDP | **on** | | UDP | **on** |
@@ -88,14 +87,23 @@ on the gaming PC. This is the path WebRTC media takes after ICE negotiation.
`auth.hetherman.cloud` to log in. Log in as a `stream-viewers` member - `auth.hetherman.cloud` to log in. Log in as a `stream-viewers` member -
you should land back at the stream page (video container + "Stream you should land back at the stream page (video container + "Stream
offline" overlay, assuming you haven't started OBS yet). offline" overlay, assuming you haven't started OBS yet).
2. **Certificate:** the padlock icon should show the Let's Encrypt cert you 2. **Certificate:** the padlock icon should show the Let's Encrypt cert.
requested. 3. **With OBS streaming**, open DevTools on the stream page and confirm:
3. **/whep, /hls, /v3:** once you start streaming in OBS, open DevTools on - `POST /whep/game-opus/whep` returns 201
the stream page and confirm requests to `/whep/game/whep`, - `GET /hls/game-opus/index.m3u8` returns 200
`/hls/game/index.m3u8`, and `/v3/paths/get/game` all return 200 (and not - `GET /status` returns 200 with `"ready": true`
401/302). 4. **UDP stream:** with OBS streaming and a viewer connected, run
4. **UDP stream:** with OBS streaming, tail the NPM container logs - you `tcpdump -n -i any udp port 48189` on the NPM host and confirm packets flow.
should see entries from the stream module for UDP connections on 48189.
Alternatively, from the NPM host run ## Troubleshooting
`tcpdump -n -i any udp port 48189` and confirm packets flow while a
viewer is connected. - **500 or auth loop on stream.hetherman.cloud:** check the outpost `proxy_pass`
in the Advanced config uses the internal Authentik address
(`http://192.168.50.224:30140`) not the public HTTPS URL. Using the public
URL causes SSL SNI issues when NPM tries to proxy to itself.
- **WHEP returns 400:** the Custom Locations tab in the NPM GUI has `/whep`,
`/hls`, or `/v3` entries. Remove them - these must only be in the Advanced
tab config so the trailing-slash path stripping works correctly.
- **WHEP returns 401 / browser shows a Basic Auth dialog:** MediaMTX is
challenging the viewer directly. Check `authInternalUsers` in `mediamtx.yml`
has a rule allowing `read` from any IP (empty `ips: []`).
+63 -43
View File
@@ -1,34 +1,46 @@
# OBS setup # OBS setup
Configures OBS Studio on the Windows gaming PC to capture the game, encode Configures OBS Studio on the Windows gaming PC to capture the game, encode
with NVENC, and publish via WHIP to the local MediaMTX instance that the OBS with NVENC H.264, and publish via RTMP to the local MediaMTX instance that the
script spawns. OBS script spawns.
Prerequisites: Prerequisites:
- OBS Studio 30.0 or newer (WHIP output is built in from 30.x onward). - OBS Studio 30.0 or newer.
- FFmpeg installed and on PATH: `winget install Gyan.FFmpeg` (admin PowerShell).
MediaMTX uses FFmpeg to transcode audio AAC->Opus for WebRTC compatibility.
- You already ran `.\scripts\install.ps1` in an elevated PowerShell, so - You already ran `.\scripts\install.ps1` in an elevated PowerShell, so
`bin\mediamtx.exe` exists and the `GameStream-UDP-48189` firewall rule is `bin\mediamtx.exe` exists and the `GameStream-UDP-48189` firewall rule is
registered (in the disabled state). registered (in the disabled state).
- **Run OBS as Administrator** so the script can toggle the Windows Firewall
rule with netsh.
## 1. Load the OBS script ## 1. Load the OBS script
1. OBS -> **Tools -> Scripts -> +** 1. OBS -> **Tools -> Scripts -> Python Settings** tab - point it at your Python
2. Select `obs-script/game_stream.py` from this repo. 3.11 installation (e.g. `C:\Users\<you>\AppData\Local\Programs\Python\Python311`).
3. In the properties panel on the right, set: If OBS shows no properties after loading the script, this is the cause.
2. OBS -> **Tools -> Scripts -> +**
3. Select `obs-script/game_stream.py` from this repo.
4. In the properties panel on the right, set:
| Setting | Value | | Setting | Value |
|-----------------------|---------------------------------------------------------------| |-----------------------|---------------------------------------------------------------|
| MediaMTX binary | `<repo>\bin\mediamtx.exe` | | MediaMTX binary | `<repo>\bin\mediamtx.exe` |
| MediaMTX config | `<repo>\config\mediamtx.yml` | | MediaMTX config | `<repo>\config\mediamtx.yml` |
| Frontend directory | `<repo>\frontend` | | Frontend directory | `<repo>\frontend` |
| Frontend HTTP port | `48080` (default) | | Frontend HTTP port | `48080` (default) |
| Firewall rule name | `GameStream-UDP-48189` (must match the rule created by install.ps1) | | Firewall rule name | `GameStream-UDP-48189` (must match the rule created by install.ps1) |
| Public URL | `https://stream.hetherman.cloud` | | Public URL | `https://stream.hetherman.cloud` |
| MediaMTX API URL | `http://127.0.0.1:19997` | | MediaMTX API URL | `http://127.0.0.1:19997` |
4. Check the **Script Log** at the bottom - you should see 5. Check the **Script Log** at the bottom - you should see:
`[game_stream] game_stream.py loaded`. - `[game_stream] MediaMTX started (pid=...)`
- `[game_stream] Frontend HTTP server listening on 0.0.0.0:48080`
- `[game_stream] game_stream.py loaded`
MediaMTX and the HTTP server start immediately on script load (not on
streaming started), so they are ready before OBS attempts RTMP.
## 2. OBS output settings ## 2. OBS output settings
@@ -38,32 +50,26 @@ Prerequisites:
| Setting | Value | | Setting | Value |
|------------------|---------------------------------------------------------| |------------------|---------------------------------------------------------|
| Audio Encoder | Opus (or FFmpeg AAC if Opus is unavailable - Opus is preferred for WebRTC) | | Audio Encoder | FFmpeg AAC (only option; MediaMTX+FFmpeg transcodes to Opus for WebRTC) |
| Video Encoder | **NVIDIA NVENC HEVC** or **NVIDIA NVENC H.264** | | Video Encoder | **NVIDIA NVENC H.264** |
Use H.264 for maximum browser compatibility (all browsers). HEVC works in Use H.264 for maximum browser compatibility (all browsers). HEVC works in
Safari and recent Chrome but not Firefox - stick with H.264 unless you have a Safari and recent Chrome but not Firefox.
specific reason.
**Encoder settings (H.264):** **Encoder settings (H.264):**
| Setting | Value | | Setting | Value |
|---------------------|-------------------| |---------------------|-------------------|
| Rate Control | CBR | | Rate Control | CBR |
| Bitrate | 8000 Kbps | | Bitrate | 8000-16000 Kbps |
| Keyframe Interval | 2 s | | Keyframe Interval | 2 s |
| Preset | P5 (Quality) | | Preset | P5 (Quality) |
| Tuning | Ultra Low Latency | | Tuning | Ultra Low Latency |
| Multipass | Two Passes (Quarter Resolution) | | Multipass | Two Passes (Quarter Resolution) |
| Profile | high | | Profile | high |
| Look-ahead | off | | Look-ahead | off |
| Psycho Visual Tuning | on |
| GPU | 0 |
| Max B-frames | **0** (required for low-latency WebRTC) | | Max B-frames | **0** (required for low-latency WebRTC) |
With a 600 Mbps upload and up to 6 viewers at 8 Mbps each, 8000 Kbps leaves
generous headroom. Push to 12000-15000 Kbps if you want higher quality.
### Audio tab ### Audio tab
| Setting | Value | | Setting | Value |
@@ -78,9 +84,11 @@ generous headroom. Push to 12000-15000 Kbps if you want higher quality.
| Setting | Value | | Setting | Value |
|----------|-----------------------------------------------| |----------|-----------------------------------------------|
| Service | Custom | | Service | Custom |
| Protocol | **WHIP** | | Server | `rtmp://127.0.0.1/game` |
| Server | `http://localhost:48889/game/whip` |
| Bearer Token | (leave blank) | > **Note:** Use RTMP (not WHIP). WHIP uses WebRTC which requires ICE/UDP
> negotiation between OBS and MediaMTX - this fails over localhost due to NAT
> hairpin issues. RTMP is plain TCP and works reliably on localhost.
Save. Save.
@@ -88,21 +96,25 @@ Save.
1. Click **Start Streaming**. 1. Click **Start Streaming**.
2. Check the OBS Script Log - you should see: 2. Check the OBS Script Log - you should see:
- `Firewall rule 'GameStream-UDP-48189' ENABLED` - `[game_stream] Firewall rule 'GameStream-UDP-48189' ENABLED`
- `MediaMTX started (pid=...)` - `[game_stream] OBS streaming started -> viewers can watch at: https://stream.hetherman.cloud`
- `Frontend HTTP server listening on 0.0.0.0:48080` 3. After 2-3 seconds, MediaMTX spawns FFmpeg to transcode the audio. The
- `Viewers can watch at: https://stream.hetherman.cloud` `game-opus` path becomes ready. The stream page will show **LIVE**.
3. Open `https://stream.hetherman.cloud` from another device, log in with 4. Open `https://stream.hetherman.cloud` from another device, log in with
Authentik, and verify video plays. Authentik, and verify video and audio play. Click the **"Click to play"**
or **"Click to unmute"** button in the status bar if audio is muted.
## 5. Stopping ## 5. Stopping
Click **Stop Streaming** in OBS. The script will: Click **Stop Streaming** in OBS. The script will:
- Stop the MediaMTX subprocess
- Stop the frontend HTTP server
- Disable the firewall rule (`GameStream-UDP-48189` -> disabled) - Disable the firewall rule (`GameStream-UDP-48189` -> disabled)
When OBS exits, the script also:
- Stops the MediaMTX subprocess (and with it, the FFmpeg transcoder)
- Stops the frontend HTTP server
Verify the firewall state from PowerShell: Verify the firewall state from PowerShell:
```powershell ```powershell
@@ -113,20 +125,28 @@ Should report `False` while not streaming, `True` while streaming.
## Troubleshooting ## Troubleshooting
- **No properties panel after loading script:** OBS is using the Microsoft
Store Python stub. Install real Python 3.11 via `winget install Python.Python.3.11`
and point OBS Python Settings to the install directory.
- **"MediaMTX binary not found"** in the script log: the path in the script - **"MediaMTX binary not found"** in the script log: the path in the script
properties panel is wrong. Re-select it with the file picker. properties panel is wrong. Re-select it with the file picker.
- **OBS cannot connect to WHIP**: MediaMTX did not start. Check the script - **OBS cannot connect / streaming fails immediately:** MediaMTX did not start.
log for the actual reason; most commonly a port conflict on 48889 or 48189 Check the script log for errors. Most commonly a port conflict on 1935, 48889,
(another process is already using them). or 48189 (another process is already using them). Also check `bin\mediamtx.log`.
- **Stream page shows "Connecting..." but never goes LIVE:** FFmpeg failed to
start the `game-opus` transcoder. Check `bin\mediamtx.log` for FFmpeg errors.
Verify `ffmpeg -version` works in a new PowerShell (not the same session as
before installing FFmpeg).
- **Viewers see "Stream offline"** even after you click Start Streaming: - **Viewers see "Stream offline"** even after you click Start Streaming:
- Check that the MediaMTX API returns `ready: true`: - Check that the MediaMTX API returns `ready: true` for the transcoded path:
`curl http://localhost:19997/v3/paths/get/game` `curl http://localhost:19997/v3/paths/get/game-opus`
- Check OBS's own streaming indicator - if it's red, OBS is not actually - Check OBS's own streaming indicator - if it's red, OBS is not connected.
sending to WHIP. Verify the URL and that the custom service / WHIP Verify the RTMP server URL is `rtmp://127.0.0.1/game`.
protocol is selected.
- **Viewers connect but playback freezes after a few seconds:** the UDP port - **Viewers connect but playback freezes after a few seconds:** the UDP port
path is broken. Verify the firewall rule is enabled (`Get-NetFirewallRule`), path is broken. Verify the firewall rule is enabled (`Get-NetFirewallRule`),
the router port-forward to NPM for UDP 48189 is correct, and the NPM Stream the router port-forward for UDP 48189 goes to NPM, and the NPM Stream entry
entry points at `<PC-LAN-IP>:48189`. points at `<PC-LAN-IP>:48189`.
- **Autoplay is blocked / no audio:** browsers start the video muted so - **No audio:** the "Click to play" or "Click to unmute" button appears in the
autoplay works. There is a "Click to unmute" button in the status bar. status bar when the browser blocks autoplay. Click it once. If audio is still
absent, check `bin\mediamtx.log` to confirm FFmpeg started successfully and
the `game-opus` path shows both H264 and Opus tracks.