Commit graph

16 commits

Author SHA1 Message Date
228ed4b09b test(webrtc): Source Subscribe pre-fill, Close, and EnableKeyFrameCache
Some checks failed
ci / vet + build (push) Failing after 5m0s
ci / race tests (push) Has been skipped
ci / WebRTC smoke (5-viewer fanout) (push) Has been skipped
ci / WebRTC latency p95 gate (push) Has been skipped
Covers:
- Subscribe pre-fills channel from IDR cache immediately on call
- No pre-fill when cache is not enabled
- Labeled-break stops pre-fill when bufDepth < burst length
- Close closes all subscriber channels (no goroutine leak)
- EnableKeyFrameCache is idempotent (second call is a no-op)
2026-05-10 09:23:40 -04:00
293536563f test(webrtc): unit tests for keyFrameCache and isH264IDRStart
Some checks failed
ci / race tests (push) Blocked by required conditions
ci / WebRTC smoke (5-viewer fanout) (push) Blocked by required conditions
ci / WebRTC latency p95 gate (push) Blocked by required conditions
ci / vet + build (push) Has been cancelled
Covers:
- isH264IDRStart: empty, single-NAL IDR (type 5), single-NAL non-IDR
  (SPS/PPS/P-frame), FU-A start IDR, FU-A start non-IDR, FU-A
  continuation, truncated FU-A, Opus payload
- push/snapshot: IDR reset, burst accumulation, double-IDR reset
- Capacity caps: maxPackets, maxBytes
- Snapshot independence: copy isolated from subsequent mutations
- Concurrent safety: 1 writer + 4 readers (-race clean)
2026-05-10 09:23:12 -04:00
020a1800ce feat(webrtc): wire keyframe cache into Source (issue #17)
Some checks failed
ci / race tests (push) Blocked by required conditions
ci / WebRTC smoke (5-viewer fanout) (push) Blocked by required conditions
ci / WebRTC latency p95 gate (push) Blocked by required conditions
ci / vet + build (push) Has been cancelled
- Add cache *keyFrameCache field to Source (nil by default).
- Add EnableKeyFrameCache() — call before Start() on video sources to
  activate IDR burst caching.
- readLoop: call cache.push(pkt) after each successful unmarshal, before
  the subscriber fanout. No lock held at push time — push acquires its
  own mutex internally.
- Subscribe: snapshot the cache outside s.mu to avoid any cross-lock
  complexity, then pre-fill the new channel with the burst before
  registering it in the subscriber set. Uses a labeled break to stop
  pre-filling if the channel is full (bufDepth too small for the burst;
  the subscriber will wait for the next live keyframe instead).
2026-05-09 19:04:17 -04:00
a2e0a8c083 feat(webrtc): add H.264 keyframe burst cache (issue #17)
Some checks failed
ci / race tests (push) Blocked by required conditions
ci / WebRTC smoke (5-viewer fanout) (push) Blocked by required conditions
ci / WebRTC latency p95 gate (push) Blocked by required conditions
ci / vet + build (push) Has been cancelled
Introduces keyFrameCache — a bounded ring buffer that retains all RTP
packets from the most recent H.264 IDR NAL unit until the packet just
before the next one. New WHEP subscribers receive this burst immediately
on Subscribe(), cutting first-frame latency from up to one IDR interval
(typically ~2 s at GOP=60/30fps) to nearly zero.

Design notes:
- Detection covers single-NAL (type 5) and FU-A start (type 28, start
  bit set, inner type 5). STAP-A IDR leading is not handled — FFmpeg
  never uses STAP-A for IDR slices in practice.
- Bounded at 512 packets / 2 MiB per source to cap memory per stream.
- push() is called only from the single-goroutine readLoop; the lock
  it holds is tiny and brief.
- snapshot() returns a shallow copy; *rtp.Packet values are immutable
  after being placed in the cache so sharing is safe.
2026-05-09 19:03:33 -04:00
01c456cd1a feat(core/webrtc): add IngestPeer for WHIP publish side (issue #16)
Some checks failed
ci / vet + build (push) Failing after 5m3s
ci / race tests (push) Has been skipped
ci / WebRTC smoke (5-viewer fanout) (push) Has been skipped
ci / WebRTC latency p95 gate (push) Has been skipped
IngestPeer is the symmetric inverse of the WHEP Peer:
- Creates a recvonly PeerConnection (Pion receives tracks from the publisher)
- OnTrack -> reads RTP packets from the remote track and writes them to
  loopback UDP ports (videoPort, audioPort) that FFmpeg is listening on
- Full lifecycle: Done(), Connected(), Close(), AddICECandidate()

PeerFactory.CreateIngestPeer() follows the same ctx/offer/ICE-gather
pattern as CreatePeerFromSources() so the app/webrtc handler layer can
use a uniform error matrix.
2026-05-09 16:20:09 -04:00
70324aad28 feat(webrtc): add Connected() channel to Peer for ICE establishment timing
Some checks failed
ci / race tests (push) Blocked by required conditions
ci / WebRTC smoke (5-viewer fanout) (push) Blocked by required conditions
ci / WebRTC latency p95 gate (push) Blocked by required conditions
ci / vet + build (push) Has been cancelled
2026-05-06 15:55:42 -04:00
4f84c72c85 feat(core/webrtc): expose Peer.Done() channel + AddICECandidate
Two small additions to support the M3 handler:

- Peer.Done() — read-only view of the existing 'done' channel,
  closed on Close(). Lets external indexes (Handler, admin API)
  await peer teardown without polling.
- Peer.AddICECandidate — passthrough so the WHEP PATCH handler
  can forward trickle-ICE candidates without reaching into the
  PeerConnection directly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 11:23:55 +00:00
9d38e9ccdb feat(webrtc): add app/webrtc subsystem + lifecycle hooks
Introduces the subsystem layer that sits alongside api.API and wires
the M1 core/webrtc primitives into the per-process restream lifecycle.

app/webrtc/subsystem.go:
  - Subsystem struct holding the global WebRTC config, core PeerFactory,
    per-process stream map, and logger
  - New(config.DataWebRTC, logger) constructor
  - Enabled(), Hooks(), Close(), lookup() methods

app/webrtc/lifecycle.go:
  - onProcessStart: allocates an adjacent UDP port pair, binds two
    Pion Sources (video on V, audio on V+1), registers them under the
    process id, and returns the two RTP output legs to append to the
    FFmpeg command.
  - onProcessStop: tears down the pair.
  - allocAdjacentPair: retries up to 10 times to find a free (V, V+1)
    pair since the kernel's ephemeral picker can hand us an odd port.
  - splitRTPLegs: converts BuildArgs' flat []string into two ConfigIO
    entries by splitting on the second -map token.

core/webrtc/peer.go + forward.go:
  - Adds PeerFactory.CreatePeerFromSources for the M2 two-source
    forwarding mode (video and audio on separate UDP ports, no
    payload-type sniffing). Leaves CreatePeer intact for the M1 PoC.
  - Adds forwardRTPSplit companion goroutine.

config/data.go:
  - Promote anonymous WebRTC struct to named type DataWebRTC so
    app/webrtc can accept it by value.
2026-04-17 10:02:00 -04:00
9e3f031f95 feat(webrtc): add -rtp-host flag + TrueNAS Docker deploy
Some checks failed
tests / build (push) Failing after 3s
CodeQL / Analyze (pull_request) Failing after 3s
tests / build (pull_request) Failing after 3s
- core/webrtc: NewSourceOn(streamID, host, port) allows binding the
  RTP UDP socket on something other than 127.0.0.1, required when the
  PoC runs in a container and must accept RTP from LAN publishers.
  NewSource(streamID, port) stays as a convenience wrapper on
  127.0.0.1 for existing tests and tight local tests.

- cmd/webrtc-poc: new -rtp-host flag (default 127.0.0.1 for safety).

- deploy/docker/Dockerfile: two-stage build, scratch runtime, ~14 MB.

- deploy/truenas/docker-compose.yml: host-networked stack template
  driven by a .env file. Host networking is required for WebRTC ICE
  to work without NAT rewriting per-candidate.

- deploy/truenas/README.md: operator runbook with port picking,
  bring-up, verification curls, and security notes.
2026-04-17 09:05:37 -04:00
f6ddae23c9 feat(webrtc): add WHEP POST handler (happy path) 2026-04-17 08:48:06 -04:00
b2a691186c feat(webrtc): add PeerFactory, Peer, and RTP forwarder 2026-04-17 08:47:27 -04:00
917c353e03 feat(webrtc): add ICE config helper (Configuration + SettingEngine)
Vendors github.com/pion/webrtc/v4 v4.2.11 and its transitive
dependencies (datachannel, dtls/v3, ice/v4, interceptor, logging,
mdns/v2, sctp, sdp/v3, srtp/v3, stun/v3, transport/v4, turn/v4).
2026-04-17 08:46:27 -04:00
1fdc29ace1 feat(webrtc): add Source with UDP RTP reader and subscriber fan-out
Adds github.com/pion/rtp v1.10.1 as a direct dependency (vendored).
2026-04-17 08:45:48 -04:00
3a17e543c5 feat(webrtc): add thread-safe Registry for stream_id -> SourceHandle 2026-04-17 08:44:59 -04:00
2250cb0a8f feat(webrtc): add Config with defaults and validation 2026-04-17 08:44:30 -04:00
7ea1844869 feat(webrtc): add package skeleton and typed errors 2026-04-17 08:43:57 -04:00