# 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. A normal stop, server failure, expiry,
  Companion exit, WebSocket loss, or OBS restart all use the same restore path.
- Core supervises one FFmpeg receiver process and creates a short rolling HLS
  window. HLS.js provides authenticated Chromium playback; native HLS remains
  available where the browser supports it.
- Stable transcription captions are reused as a private WebVTT side channel.
  No second transcription pipeline and no local speech inference are added.

Temporary HLS fragments exist only to serve the active admin player. They are
bounded, deleted as they roll out, and removed when the session ends. Lumi does
not retain a raw stream recording.

## Server setup

Install a current FFmpeg build and ensure the Lumi service account can execute
it. Set:

```text
LUMI_STREAM_TEST_INGEST_HOST=the-hostname-reachable-from-the-streaming-computer
```

Optional controls:

```text
LUMI_FFMPEG_PATH=/absolute/path/to/ffmpeg
LUMI_STREAM_TEST_INGEST_PORT=19350
LUMI_STREAM_TEST_PUBLIC_PORT=443
LUMI_STREAM_TEST_ENCODER=h264_nvenc
LUMI_STREAM_TEST_MAX_MS=1800000
LUMI_STREAM_TEST_INACTIVITY_MS=45000
LUMI_STREAM_TEST_MAX_BYTES=1073741824
```

Allow the configured ingest port only from trusted streaming networks. The
default RTMP hop uses a 256-bit, session-scoped credential and never exposes the
normal stream key, but RTMP itself is not encrypted. Put RTMPS at the network
boundary, set `LUMI_STREAM_TEST_RTMPS=true`, and set
`LUMI_STREAM_TEST_PUBLIC_PORT` to the proxy's external port. FFmpeg continues
listening on the private `LUMI_STREAM_TEST_INGEST_PORT`.
The player, playlists, segments, status, and stop endpoints always require an
active Lumi administrator session.

Lumi automatically selects `h264_nvenc` when the installed FFmpeg advertises
it, otherwise it uses bounded `libx264`. NVIDIA acceleration has no artificial
model lock: an RTX 3060 12 GB is a supported production baseline, faster cards
such as a 3080 Ti work without configuration changes, and CPU fallback remains
available. The ladder contains the source plus 720p and 480p only when the
source is larger, so it never upscales.

## 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, inactivity, temporary-storage limit,
process failure, server shutdown, Companion disconnect, or an administrator
stop. One active session is allowed today; UUID session identities and scoped
paths are already used throughout so the model can be expanded later.

## Deterministic verification

**Run test pattern** on the Admin page creates a 1280×720 moving color/timing
pattern with a stable 880 Hz audio tone. It exercises receiver startup,
transcoding, adaptive HLS, audio, quality switching, player cleanup, and
diagnostics without OBS. It uses the same limits and temporary-media cleanup as
a Companion session.

The focused checks are:

```text
npm run verify:stream-testing
dotnet run --project companion/tests/Lumi.Companion.Core.Tests/Lumi.Companion.Core.Tests.csproj
```
