Skip to content

Agent: [Agent] WebRTC: fix ICE gathering/trickle and remove dead signaling code - #661

Merged
matteius merged 1 commit into
opensensor:mainfrom
matteius:agent/issue-659-39054207904
Oct 17, 2026
Merged

matteius merged 1 commit into
opensensor:mainfrom
matteius:agent/issue-659-39054207904

Conversation

@matteius

Copy link
Copy Markdown
Contributor

Refs #659

Agent-generated change from a maintainer-labeled issue. Review the diff and CI results before merging. Close the issue only when its acceptance criteria are complete.

DigitalOcean session: lightnvr-public-issue-659-39054207904.

…sor#659)

Problem
-------
WebRTCVideoCell.jsx contained a commented-out trickle-ICE block that POSTed
candidates to /api/webrtc/ice, a route that exists in neither LightNVR nor
go2rtc's HTTP API. Worse, the log line said "waiting for ICE gathering..."
but the code did not wait at all: the offer was POSTed to
{go2rtcBaseUrl}/api/webrtc?src=... immediately after setLocalDescription(),
so the SDP typically carried zero ICE candidates. Meanwhile pc.onicecandidate
and pc.onicegatheringstatechange logged every candidate and state transition
with console.log, which floods the console on a 16-camera grid.

Findings (current flow, measured)
---------------------------------
Verified against go2rtc v1.9.14 with an ffmpeg test source, headless
Chromium, and a proxy that mimics LightNVR's /go2rtc/ reverse proxy
(median of 3 runs per scenario, timestamps relative to page load):

- OLD flow: offer POSTed ~25ms after local description set with **0
  candidates** in the SDP (pool size 0, gathering races the fetch).
- On a LAN (empty iceServers): ICE connected ~60ms, first frame ~650ms.

Change (option (b) from the issue)
----------------------------------
Signaling stays a single HTTP POST to go2rtc /api/webrtc, but the offer is
now sent when ICE gathering completes **or a 1s bound expires**, whichever
comes first, via a new waitForIceGathering() helper
(web/js/utils/webrtc-ice-gathering.js). The cell abort signal also ends the
wait so unmount/retry can never hang. Host candidates now actually reach
go2rtc inside the offer, and an unreachable STUN/TURN server can only stall
the connect by 1s instead of the browser's multi-second gathering timeout.

Option (a) (real trickle over go2rtc's /api/ws WebSocket) was rejected: the
/go2rtc/ reverse proxy in lightNVR is an HTTP-only libcurl proxy
(src/web/api_handlers_go2rtc_proxy.c, go2rtc_proxy_thread.c) with no
WebSocket upgrade support, so it would require a non-trivial backend change
for marginal benefit over the bounded wait. This change is frontend-only.

- Dead commented trickle block deleted; comments now explain why there is
  no trickle channel.
- Per-candidate and gathering-state logs moved from console.log to
  console.debug (existing convention, e.g. auth-utils.js) — quiet by
  default, still visible with devtools verbose logging.

Measured results (before/after)
-------------------------------
median of 3 runs, Chromium headless, LAN:

  scenario                                gather  cands  ICE conn  frame
  OLD no wait,   no STUN                  0ms     0      60ms      664ms
  NEW bounded 1s, no STUN                 117ms   2*     168ms     697ms
  OLD no wait,   bogus STUN               0ms     0      48ms      734ms
  NEW bounded 1s, bogus STUN              1001ms  2*     1046ms    1686ms
  NAIVE full wait, bogus STUN             30001ms 2*     30053ms   30069ms

  (* = host candidates) — bogus STUN = stun:192.0.2.1:3478 (TEST-NET-1,
  unreachable). "NAIVE full wait" hit the 30s harness cap; a real browser
  takes ~5-7s. Bounded gathering keeps the stall at exactly 1s in that
  case and costs ~30ms on a normal LAN (negligible vs 660ms TTFF), while
  the offer now carries usable host candidates in both cases.
  All 5 scenarios connected 3/3 through the /go2rtc/ proxy path.

Validation
----------
- cd web && npx vite build: passes (version.js pre-generated by
  scripts/extract_version.js, as scripts/build_web_vite.sh does).
- npm run lint: 0 errors (only pre-existing a11y warnings).
- npm test: 373 passed; 5 failures in tests/timeline-utils.spec.js are
  date-sensitive and reproduce identically on unmodified main.
- New tests: web/tests/webrtcIceGathering.spec.js (10) for the bounded
  wait helper.

Not validated here (needs a deployed instance)
----------------------------------------------
- Manual check in Chrome and Firefox against a running lightNVR instance
  with real cameras, with and without auth. Reviewer plan: on the live
  page, confirm "waiting for ICE gathering (up to 1000ms)..." then
  "Sending offer directly to go2rtc ... (ICE gathering: complete)" and
  that first frames arrive; confirm the console shows no per-candidate
  logs at default level; re-test with a bogus ice_servers entry via
  /api/ice-servers config and confirm connect still succeeds within ~1s
  extra. TTFF before/after is available from /api/telemetry/player
  (ttff_ms) since player-telemetry already reports it.

Rollback
--------
Revert this commit; the helper module and its tests are additive only.
@codecov

codecov Bot commented Oct 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@matteius
matteius merged commit 7a2008a into opensensor:main Oct 17, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant