# Datarhei - Dragon Fork — Implementation Notes

This file tracks observations, gotchas, and decisions made during the Dragon
Fork WebRTC egress implementation. Keep entries chronological; each milestone
adds a new section.

## Baseline (M1, 2026-04-17)

- Forked from upstream `datarhei/core` commit `0de97f4` ("Add linux/arm/v8 build").
- Upstream module path: `github.com/datarhei/core/v16`. The Dragon Fork keeps
  this module path so internal imports don't churn; the fork is distinguished
  by its repo location (`forge.wilddragon.net/zgaetano/datarhei-dragonfork-core`)
  and branch history, not its Go module identity.
- Toolchain: Go 1.22.8, FFmpeg 4.4.2 in the sandbox. FFmpeg 6.x recommended
  for publishers in Task 10; 4.4.2 is sufficient for the PoC (libx264 +
  libopus + RTP muxer all present).
- `go build ./...` on the clean fork: succeeds.
- `go test -short ./...` on the clean fork: all packages pass. No upstream
  flakes observed.

### Pre-existing state of note
- None flagged.

---

## v0.2 (2026-05-06)

### Restreamer SDK gap — `UpsertIngest` does not forward `webrtc` or `whip_ingest`

The datarhei Restreamer SDK's `UpsertIngest` builds the FFmpeg process config
from `control.hls`, `control.rtmp`, `control.srt`, and `control.process`, but
silently discards `control.webrtc` and `control.whip_ingest`. This was confirmed
by inspecting the minified SDK bundle inside the running container.

**Implication:** toggling WebRTC egress or WHIP ingest from the Restreamer UI
required a monkey-patch. The Edit view (`overlay/src/views/Edit/index.js`)
overrides `props.restreamer._upsertProcess` immediately before the
`UpsertIngest` call to inject `whip_ingest.enabled` (and in future,
`webrtc.enabled`) into the process config JSON, then restores the original
method in a `finally` block. The patch is narrow and reversible.

**Why not patch the SDK?** The SDK is a vendored minified bundle inside the
upstream Restreamer UI. Patching it would require either maintaining a fork of
the minifier input (the full SDK source is not in the UI repo) or deobfuscating
and re-minifying. The monkey-patch is pragmatic and self-documenting.

### UI state uses `enable` (no d); Core API uses `enabled` (with d)

WHEP.js and WHIP.js use `enable` (matching existing convention in the UI
controls). The monkey-patch performs the mapping:

```js
config.whip_ingest = { enabled: !!(control.whip_ingest && control.whip_ingest.enable) };
```

If this causes confusion in the future, unify on `enabled` in both places.

---

## v0.3 (2026-05-10)

### `ProcessConfigWHIPIngest` was the critical backend gap

`http/api/process.go` had `ProcessConfigWebRTC` for WHEP but no corresponding
struct for WHIP. Without it, the JSON field `whip_ingest` was dropped on
unmarshal and `app.Config.WHIPIngest.Enabled` was always `false`. WHIP
could never activate via the API regardless of what the UI sent. Adding
`ProcessConfigWHIPIngest` and wiring it into `Marshal()`/`Unmarshal()` was
the essential fix (commit `4d94c88`).

### seed-data.sh no-clobber bug

`seed-data.sh` used `cp -n` (no-clobber) for all files including the built
JS bundle. On first deploy the `static/` directory doesn't exist and the copy
succeeds. On every subsequent deploy the directory already exists and the copy
is skipped — so a rebuilt container silently serves the old bundle. The symptom
was confusing: the binary had new code, but the UI didn't reflect it.

**Fix:** split into two phases. `index.html`, `asset-manifest.json`, and
`static/` are always overwritten (`cp -Rfp`). All other content (channel
database, player bundles, operator-uploaded media) remains no-clobber to
protect live data.

### Keyframe cache lock ordering

`keyFrameCache` has its own mutex `c.mu` distinct from `Source.mu`. The two
locks are never nested:

- `readLoop` calls `cache.push(pkt)` (acquires/releases `c.mu`), then
  acquires `s.mu` for the subscriber fanout — sequential, not nested.
- `Source.Subscribe()` takes the snapshot outside `s.mu` (acquires/releases
  `c.mu`), then acquires `s.mu` to register the subscriber — also sequential.

This means there is no deadlock risk even though two separate mutexes are
involved. The snapshot-before-lock pattern in `Subscribe` was chosen for
clarity, not necessity; but it documents the intent explicitly.

### STAP-A IDR detection is not implemented

`isH264IDRStart` handles single-NAL (type 5) and FU-A start (type 28, start
bit, inner type 5). It does **not** handle STAP-A aggregates (type 24) that
happen to lead with an IDR NAL.

In practice, FFmpeg and GStreamer never emit IDR slices inside STAP-A — IDR
frames are large and STAP-A is designed for small NALs (SPS + PPS combos).
If a publisher that does use STAP-A for IDR ever appears, the cache will miss
the keyframe boundary; the worst outcome is a larger-than-expected burst (the
cache grows until the next correctly-detected IDR) rather than a crash or
incorrect video. Add STAP-A handling in a future revision if needed.

### `go test -race ./core/webrtc/...` baseline

All 34 tests in `core/webrtc` pass under the race detector as of v0.3
(commit `228ed4b`). The suite covers config, ICE, registry, peer creation,
WHEP handler, keyframe cache, and Source subscribe/pre-fill/close. Total
runtime ≈ 16 s (dominated by the two 5-second ICE gathering timeouts in
`TestPeerFactory_*`).
