# Testing the WebRTC egress path

## In-process (CI)

```sh
go test -race -count=1 ./app/webrtc/... ./core/webrtc/...
```

The integration tests under `app/webrtc/` allocate UDP ports on
loopback, spin up an Echo handler, attach a Pion subscriber, and
spray synthetic RTP into the registered Source. `TestIntegration_FiveViewerFanout`
covers the 5-concurrent-viewer acceptance path from the M3 design.

## Manual / browser

`whep-player.html` is a self-contained WHEP subscriber a human can
point at any live deploy. Open it directly in a browser:

```
file:///path/to/datarhei-dragonfork-core/test/whep-player.html
```

…or copy it onto a static host (no server-side dependency). It accepts
the WHEP URL and an optional bearer token (the deploy uses Core's
JWT, so paste an `access_token` from `POST /api/login`). It POSTs an
SDP offer with a recvonly video + audio transceiver, applies the
answer, and renders the stream in `<video>`. Stats panel shows ICE +
PeerConnection states, the codec pulled from the answer SDP, and a
1-Hz inbound-bitrate sample. Disconnect issues a WHEP `DELETE` on
the resource URL the server returned in `Location`.

Shareable URL:

```
file:///.../whep-player.html?url=http://10.0.0.25:8090/api/v3/whep/myStream&token=eyJhbGciOi...
```

## Pion CLI helper

`test/whep-client/` is the same handshake in Go, useful for scripting
or running on the same machine as Core for an apples-to-apples loopback
test:

```sh
cd test/whep-client
go build -o /tmp/whep-client .
/tmp/whep-client -url http://10.0.0.25:8090/api/v3/whep/myStream -token "$JWT" -timeout 15s
```

Exits 0 once both video and audio tracks have received their first
RTP packet. Used in the M2 deploy verification on TrueNAS.

## Latency p95 gate

Wired into CI via the `latency-gate` job in `.forgejo/workflows/test.yml`.
Run locally:

```sh
go test -tags latency -timeout 90s -race -count=1 \
  -run TestLatencyServerHop ./app/webrtc/...
```

### What it measures

Server-hop latency from `corewebrtc.Source` ingest through Pion's
DTLS-SRTP egress to a subscriber's `track.ReadRTP()`. The publisher
embeds a wall-clock UnixNano timestamp in each RTP payload; the
subscriber reads it on arrival and diffs.

### What it does NOT measure

True glass-to-glass latency would include FFmpeg encode and a real
H.264 decoder on the subscriber side. The design (`webrtc-design.md`
§7) calls for `drawtext`-burned frame counters + decode-side pixel
sampling; implementing that in pure Go would require a cgo H.264
decoder or an FFmpeg-as-sidecar pipe, neither of which pays off for
the dominant CI question (*"did anybody regress the server hop?"*).
Encode/decode latency is fixed by the codec stack — Core code changes
won't move it.

### Threshold

`p95 < 50 ms` on the CI runner. Locally observed on a quiet host:
`p50 ≈ 110 µs`, `p95 ≈ 240 µs`, `p99 ≈ 320 µs`. The 50ms gate is two
orders of magnitude above that — generous, but a regression that
crosses it indicates a genuine slowdown rather than runner noise.
