147 lines
7.0 KiB
Markdown
147 lines
7.0 KiB
Markdown
# Private stream testing
|
|
|
|
Lumi Stream Testing previews the real OBS output without publishing it to the
|
|
normal stream destination. Start it from the core **Stream testing** page in
|
|
Lumi Companion, then watch it at **Admin > Stream testing**.
|
|
|
|
## Architecture
|
|
|
|
- The existing paired-device WebSocket authenticates session creation, OBS
|
|
metrics, caption cues, stop requests, and server-ended notifications.
|
|
- The existing same-user OBS Bridge snapshots the complete current OBS service,
|
|
temporarily installs an expiring custom service, reports real output facts,
|
|
and restores the snapshot.
|
|
- Companion protects the minimum recovery record with Windows DPAPI and writes
|
|
it atomically before OBS is redirected. Normal stop, server failure, expiry,
|
|
Companion exit, WebSocket loss, and OBS restart all use that restore path.
|
|
- Core lazily starts a Lumi-managed MediaMTX process. MediaMTX accepts the
|
|
private RTMP(S) publisher and repackages the original source into low-latency
|
|
HLS without transcoding, scaling, a GPU requirement, or an adaptive ladder.
|
|
- The MediaMTX API, metrics, and HLS listeners bind only to loopback. The Admin
|
|
WebUI accesses HLS through an authenticated, no-store, same-origin streaming
|
|
proxy. RTSP, WebRTC, SRT, playback, pprof, and recording are disabled.
|
|
- Companion explicitly starts a server-hosted `stream_test` transcription
|
|
session before redirecting OBS. It reuses the selected source and model while
|
|
keeping normal live-caption delivery disabled. Interim revisions progressively
|
|
reveal each caption in the private player; a finalized caption remains for a
|
|
length-aware readability window, then ends when stale or when the next
|
|
utterance replaces it. The player's Captions control toggles a wrapping,
|
|
multiline Lumi caption layer immediately as revisions arrive instead of
|
|
delaying display against the HLS media timeline. No second transcription
|
|
pipeline and no local speech inference are added.
|
|
|
|
Media segments stay in bounded MediaMTX memory and roll out of the short live
|
|
window. Lumi does not retain a raw stream recording or create temporary HLS
|
|
files. Session state, captions, and diagnostic facts disappear when the test
|
|
ends or Lumi restarts.
|
|
|
|
## Managed setup
|
|
|
|
No operating-system package installation, FFmpeg installation, or executable
|
|
path is required. Open **Admin > Stream testing** and choose **Install
|
|
MediaMTX**. Lumi then:
|
|
|
|
1. downloads the pinned official Windows x64 archive only after confirmation;
|
|
2. enforces download and extraction size limits;
|
|
3. verifies the exact SHA-256 checksum and archive layout;
|
|
4. probes the executable and exact expected version; and
|
|
5. atomically installs it while retaining the previous installation until the
|
|
replacement is known to be valid.
|
|
|
|
**Repair installation** repeats the same verified workflow. **Run health
|
|
check** starts MediaMTX with deny-by-default paths and tests its loopback API,
|
|
metrics, and HLS listeners; opening the page alone never downloads or starts a
|
|
runtime. `LUMI_MEDIAMTX_PATH` remains an advanced manual override for another
|
|
platform or operator-managed binary, but normal Windows installations do not
|
|
need it.
|
|
|
|
The managed release and checksum source are recorded in
|
|
`src/services/mediamtx-runtime-manifest.json`. MediaMTX's licence is retained in
|
|
the downloaded runtime and documented in
|
|
[`third-party/MediaMTX-MIT.txt`](third-party/MediaMTX-MIT.txt).
|
|
|
|
## Ingest network configuration
|
|
|
|
Lumi derives the network policy from the authenticated Companion pairing
|
|
origin. A Companion paired through `localhost`, `127.0.0.1`, or `::1` always
|
|
receives direct loopback RTMP on the configured local listener port. A
|
|
non-local Companion must have paired through HTTPS and always receives RTMPS
|
|
at the same paired Lumi hostname.
|
|
|
|
In production, Nginx Proxy Manager owns the public certificate and terminates
|
|
TLS:
|
|
|
|
```text
|
|
OBS → rtmps://<paired Lumi hostname>:1936
|
|
→ Nginx Proxy Manager decrypts TLS
|
|
→ plain RTMP to Lumi:19350
|
|
```
|
|
|
|
MediaMTX binds plain RTMP on `0.0.0.0:19350` for a production session. It never
|
|
requests, loads, renews, or stores a TLS certificate. Configure an NPM TCP
|
|
stream that listens on public port `1936` with TLS enabled and forwards to the
|
|
Lumi host on port `19350`. The WebUI reverse-proxy settings can change both
|
|
ports; values are stored in Lumi's settings database and apply to the next
|
|
session.
|
|
|
|
The collapsed **Reverse-proxy ingest** section in **Admin > Stream testing**
|
|
also provides **Check external access**. The check connects to the current
|
|
public Lumi hostname and port with TLS and SNI, validates the public
|
|
certificate, then completes an RTMP handshake through NPM to MediaMTX. A
|
|
server-side failure can still mean that the router does not support NAT
|
|
loopback/hairpinning, so confirm the path from an external network when the NPM
|
|
and firewall configuration otherwise look correct.
|
|
|
|
Every session receives an exact `lumi-test/<uuid>` path and high-entropy
|
|
publisher credentials. Only that path can be published, and the generated
|
|
MediaMTX configuration contains SHA-256 credential hashes rather than
|
|
plaintext credentials.
|
|
|
|
RTMPS is required for every non-local pairing. Unencrypted RTMP is advertised
|
|
only to an authenticated loopback pairing. Use the existing paired Lumi
|
|
hostname; no extra Stream Testing domain is required. Restrict direct access
|
|
to the local MediaMTX listener with the host firewall so production publishers
|
|
must enter through NPM.
|
|
|
|
Optional session limits remain:
|
|
|
|
```text
|
|
LUMI_STREAM_TEST_MAX_MS=1800000
|
|
LUMI_STREAM_TEST_INACTIVITY_MS=45000
|
|
```
|
|
|
|
## Safety and recovery
|
|
|
|
Companion refuses to start while OBS is streaming or recording. During a test
|
|
its navigation status reads **PRIVATE TEST ACTIVE**. The DPAPI recovery file is
|
|
removed only after OBS confirms that the saved service type and complete
|
|
settings JSON were restored and saved.
|
|
|
|
If OBS was closed or unavailable during cleanup, open OBS and leave Companion
|
|
running. Automatic restoration resumes when the same-user bridge reconnects.
|
|
The **Repair restoration** action retries the same operation and does not create
|
|
a new session.
|
|
|
|
The receiver stops on maximum duration, lack of inbound media, process failure,
|
|
server shutdown, Companion disconnect, or administrator stop. OBS status
|
|
messages do not reset the media-inactivity clock. Only one session is allowed.
|
|
MediaMTX is reconfigured to a deny-by-default path set after every session.
|
|
|
|
## Diagnostics and verification
|
|
|
|
The Admin page reports real OBS bitrate, dropped frames, congestion, MediaMTX
|
|
path readiness, media tracks, inbound/outbound bytes, HLS viewers, discarded
|
|
frames, player latency, buffer health, stalls, errors, and caption delay.
|
|
Only an allowlist of MediaMTX API and metrics fields is exposed. Publisher
|
|
credentials are redacted from status, timeline entries, process logs, and
|
|
errors.
|
|
|
|
The runtime health check replaces the former transcoded synthetic test pattern.
|
|
Focused verification uses a fake MediaMTX process with real loopback API,
|
|
metrics, and HLS listeners:
|
|
|
|
```text
|
|
npm run verify:stream-testing
|
|
dotnet run --project companion/tests/Lumi.Companion.Core.Tests/Lumi.Companion.Core.Tests.csproj
|
|
```
|