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
+37 -3
View File
@@ -11,8 +11,6 @@ have admin access.
1. Authentik admin -> **Directory -> Groups -> Create**
2. Name: `stream-viewers`
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
@@ -68,7 +66,43 @@ curl -I https://auth.hetherman.cloud/outpost.goauthentik.io/ping
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`:
+36 -28
View File
@@ -8,7 +8,7 @@ Configures NPM to:
**Stream** (L4 UDP proxy).
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
@@ -22,7 +22,7 @@ Make sure your router forwards these to NPM (not to the PC directly):
| Proto | External port | Internal target |
|-------|--------------|-------------------|
| 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
the new one for this app.)
@@ -38,20 +38,15 @@ In NPM, **Hosts -> Proxy Hosts -> Add Proxy Host**.
| Domain Names | `stream.hetherman.cloud` |
| Scheme | `http` |
| Forward Hostname | `<PC-LAN-IP>` |
| Forward Port | `48080` |
| Forward Port | `48080` |
| Cache Assets | off |
| 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
API are reverse-proxied to the right MediaMTX ports (and inherit the same
forward-auth gating).
| Location | Scheme | Forward Hostname | Forward Port |
|----------|--------|------------------|--------------|
| `/whep` | `http` | `<PC-LAN-IP>` | `48889` |
| `/hls` | `http` | `<PC-LAN-IP>` | `48888` |
| `/v3` | `http` | `<PC-LAN-IP>` | `19997` |
**Custom Locations tab:** leave this **empty**. Do not add any custom location
entries here. The `/whep/`, `/hls/`, and `/v3/` locations are defined in the
Advanced tab config below with trailing-slash path stripping. Adding them in
the GUI creates duplicate nginx location blocks that cause routing failures.
**SSL tab:**
@@ -61,8 +56,12 @@ forward-auth gating).
- HSTS Enabled: optional
**Advanced tab:** paste the entire contents of
[`config/npm-advanced.conf`](../config/npm-advanced.conf). This installs the
Authentik forward-auth subrequest and the sign-in redirect.
[`config/npm-advanced.conf`](../config/npm-advanced.conf). This installs:
- 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.
@@ -72,9 +71,9 @@ In NPM, **Hosts -> Streams -> Add Stream**.
| Field | Value |
|-------------------|---------------|
| Incoming Port | `48189` |
| Incoming Port | `48189` |
| Forward Host | `<PC-LAN-IP>` |
| Forward Port | `48189` |
| Forward Port | `48189` |
| TCP | **off** |
| 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 -
you should land back at the stream page (video container + "Stream
offline" overlay, assuming you haven't started OBS yet).
2. **Certificate:** the padlock icon should show the Let's Encrypt cert you
requested.
3. **/whep, /hls, /v3:** once you start streaming in OBS, open DevTools on
the stream page and confirm requests to `/whep/game/whep`,
`/hls/game/index.m3u8`, and `/v3/paths/get/game` all return 200 (and not
401/302).
4. **UDP stream:** with OBS streaming, tail the NPM container logs - you
should see entries from the stream module for UDP connections on 48189.
Alternatively, from the NPM host run
`tcpdump -n -i any udp port 48189` and confirm packets flow while a
viewer is connected.
2. **Certificate:** the padlock icon should show the Let's Encrypt cert.
3. **With OBS streaming**, open DevTools on the stream page and confirm:
- `POST /whep/game-opus/whep` returns 201
- `GET /hls/game-opus/index.m3u8` returns 200
- `GET /status` returns 200 with `"ready": true`
4. **UDP stream:** with OBS streaming and a viewer connected, run
`tcpdump -n -i any udp port 48189` on the NPM host and confirm packets flow.
## Troubleshooting
- **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
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
script spawns.
with NVENC H.264, and publish via RTMP to the local MediaMTX instance that the
OBS script spawns.
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
`bin\mediamtx.exe` exists and the `GameStream-UDP-48189` firewall rule is
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. OBS -> **Tools -> Scripts -> +**
2. Select `obs-script/game_stream.py` from this repo.
3. In the properties panel on the right, set:
1. OBS -> **Tools -> Scripts -> Python Settings** tab - point it at your Python
3.11 installation (e.g. `C:\Users\<you>\AppData\Local\Programs\Python\Python311`).
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 |
|-----------------------|---------------------------------------------------------------|
| MediaMTX binary | `<repo>\bin\mediamtx.exe` |
| MediaMTX config | `<repo>\config\mediamtx.yml` |
| 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) |
| 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
`[game_stream] game_stream.py loaded`.
5. Check the **Script Log** at the bottom - you should see:
- `[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
@@ -38,32 +50,26 @@ Prerequisites:
| Setting | Value |
|------------------|---------------------------------------------------------|
| Audio Encoder | Opus (or FFmpeg AAC if Opus is unavailable - Opus is preferred for WebRTC) |
| Video Encoder | **NVIDIA NVENC HEVC** or **NVIDIA NVENC H.264** |
| Audio Encoder | FFmpeg AAC (only option; MediaMTX+FFmpeg transcodes to Opus for WebRTC) |
| Video Encoder | **NVIDIA NVENC H.264** |
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
specific reason.
Safari and recent Chrome but not Firefox.
**Encoder settings (H.264):**
| Setting | Value |
|---------------------|-------------------|
| Rate Control | CBR |
| Bitrate | 8000 Kbps |
| Bitrate | 8000-16000 Kbps |
| Keyframe Interval | 2 s |
| Preset | P5 (Quality) |
| Tuning | Ultra Low Latency |
| Multipass | Two Passes (Quarter Resolution) |
| Profile | high |
| Look-ahead | off |
| Psycho Visual Tuning | on |
| GPU | 0 |
| 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
| Setting | Value |
@@ -78,9 +84,11 @@ generous headroom. Push to 12000-15000 Kbps if you want higher quality.
| Setting | Value |
|----------|-----------------------------------------------|
| Service | Custom |
| Protocol | **WHIP** |
| Server | `http://localhost:48889/game/whip` |
| Bearer Token | (leave blank) |
| Server | `rtmp://127.0.0.1/game` |
> **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.
@@ -88,21 +96,25 @@ Save.
1. Click **Start Streaming**.
2. Check the OBS Script Log - you should see:
- `Firewall rule 'GameStream-UDP-48189' ENABLED`
- `MediaMTX started (pid=...)`
- `Frontend HTTP server listening on 0.0.0.0:48080`
- `Viewers can watch at: https://stream.hetherman.cloud`
3. Open `https://stream.hetherman.cloud` from another device, log in with
Authentik, and verify video plays.
- `[game_stream] Firewall rule 'GameStream-UDP-48189' ENABLED`
- `[game_stream] OBS streaming started -> viewers can watch at: https://stream.hetherman.cloud`
3. After 2-3 seconds, MediaMTX spawns FFmpeg to transcode the audio. The
`game-opus` path becomes ready. The stream page will show **LIVE**.
4. Open `https://stream.hetherman.cloud` from another device, log in with
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
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)
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:
```powershell
@@ -113,20 +125,28 @@ Should report `False` while not streaming, `True` while streaming.
## 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
properties panel is wrong. Re-select it with the file picker.
- **OBS cannot connect to WHIP**: MediaMTX did not start. Check the script
log for the actual reason; most commonly a port conflict on 48889 or 48189
(another process is already using them).
- **OBS cannot connect / streaming fails immediately:** MediaMTX did not start.
Check the script log for errors. Most commonly a port conflict on 1935, 48889,
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:
- Check that the MediaMTX API returns `ready: true`:
`curl http://localhost:19997/v3/paths/get/game`
- Check OBS's own streaming indicator - if it's red, OBS is not actually
sending to WHIP. Verify the URL and that the custom service / WHIP
protocol is selected.
- Check that the MediaMTX API returns `ready: true` for the transcoded path:
`curl http://localhost:19997/v3/paths/get/game-opus`
- Check OBS's own streaming indicator - if it's red, OBS is not connected.
Verify the RTMP server URL is `rtmp://127.0.0.1/game`.
- **Viewers connect but playback freezes after a few seconds:** the UDP port
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
entry points at `<PC-LAN-IP>:48189`.
- **Autoplay is blocked / no audio:** browsers start the video muted so
autoplay works. There is a "Click to unmute" button in the status bar.
the router port-forward for UDP 48189 goes to NPM, and the NPM Stream entry
points at `<PC-LAN-IP>:48189`.
- **No audio:** the "Click to play" or "Click to unmute" button appears in the
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.