Enable browser G2G latency measurement by default - #191
Conversation
commit: |
…ming Enabling glass-to-glass by default surfaced that the measurement never produced a single sample in Chrome. `RTCRtpContributingSource.timestamp` is specified as `performance.timeOrigin + performance.now()` — already epoch ms. The frame reader added `performance.timeOrigin` on top, putting playout ~56 years in the future so every sample tripped the `MAX_PLAUSIBLE_MS` guard in `recordFrame`. Verified live against lucy-2.5: 481/481 frame-metadata lookups hit, yet `sampleCount` stayed 0 and `g2gMs`/`ttffMs` stayed null for the whole session. After the fix the same session reports g2gMs 651, ttffMs 2476, sampleCount ramping to the 300-sample window, and `limitingFactor` correctly flipping from "bandwidth" to "latency". The unit test encoded the same wrong premise (it drove the event with a relative timestamp), which is why the bug survived; it now uses an epoch timestamp like a real browser. Also in this change: - subscribe-client: flatten the frame-timing guard, removing a dead `logger.warn` branch that was unreachable because the outer condition already required `frameTiming`. React Native now reports "this platform has no frame-metadata worker" instead of wrongly blaming absent encoded transforms. - subscribe-client: document that LiveKit owns the worker after handoff and terminates it on RoomEvent.Disconnected, so `disconnect()` must not. - media-channel: wrap the worker-creation failure in a identifiable error and classify it as permanent, so a reconnect fails fast instead of burning every retry on an environment-deterministic failure. - README: warn that viewers on an older @decartai/sdk join frame-timed rooms without a strip worker and decode nothing — a silent black screen, no error. New tests (each verified to fail under the corresponding mutation): Safari's script-transform path, the pre-created worker handoff and per-reconnect refresh, frameTiming round-tripping into the sessionStarted subscribe token, and the RN rejection routed through the real React Native entry point.
Review + live verification (Claude Code)Reviewed the diff and ran it end-to-end against 🔴 G2G measured nothing — fixed
Instrumented the live session — the LiveKit chain was perfectly healthy, only the SDK arithmetic was wrong:
Same session, before → after the one-line fix:
The unit test encoded the same wrong premise — it drove the event with a relative timestamp — which is why this survived review. It now uses an epoch timestamp like a real browser. Also in f84959c
294/294 tests,
|
Human TL;DR
Glass-to-glass latency (
g2gMs/ttffMs) now measures automatically in browserrealtime sessions instead of requiring
debugQuality: true. This is the number thatactually reflects how laggy a session feels — network RTT hides model inference time,
so a session can read "good" while feeling bad. When present, g2g drives the latency
verdict instead of RTT.
Enabling it by default surfaced that the measurement had never worked: a unit-conversion
bug meant it silently produced zero samples in every browser, so
g2gMswas alwaysnull. That's fixed here and verified against a live lucy-2.5 session (g2gMs 651,ttffMs 2476, 300-sample window).
Note for viewers: browser publishers now advertise frame timing, so anyone using
realtime.subscribeshould be on this SDK version or newer.Summary
debugQualityopt-in gate from browser glass-to-glass latency measurement; the option remains accepted as a deprecated no-op.frame_timingin subscribe tokens, attaching the frame-metadata worker for browser subscribers, and explicitly rejecting unsupported/RN frame-timed subscriptions.Details
timeSyncUpdateevents rather than a one-time startup probe. Stats snapshots expose a 300-sample rolling window after warm-up and are emitted on the existing WebRTC stats tick.Validation
pnpm test tests/realtime.unit.test.ts tests/prepare-connection.unit.test.ts tests/frame-metadata-diagnostics.unit.test.ts tests/react-native-compat.unit.test.ts(passed 10 files / 289 tests)pnpm typecheckpnpm biome check packages/sdk/src/realtime/browser/frame-metadata-diagnostics.ts packages/sdk/src/realtime/browser/index.ts packages/sdk/src/realtime/browser/preflight.ts packages/sdk/src/realtime/browser/prepare-connection.ts packages/sdk/src/realtime/client.ts packages/sdk/src/realtime/media-channel.ts packages/sdk/src/realtime/observability/connection-quality.ts packages/sdk/src/realtime/observability/webrtc-stats.ts packages/sdk/src/realtime/react-native/prepare-connection.ts packages/sdk/src/realtime/stream-session.ts packages/sdk/src/realtime/subscribe-client.ts packages/sdk/tests/built-package.mjs packages/sdk/tests/frame-metadata-diagnostics.unit.test.ts packages/sdk/tests/prepare-connection.unit.test.ts packages/sdk/tests/react-native-compat.unit.test.ts packages/sdk/tests/realtime.unit.test.tspnpm build(passes; existing tsdowndefineoption warning still prints)pnpm test:packagegit diff --checklucy-2.5(Playwright + Chrome fake camera): publisherreports non-null g2g, same-version viewer decodes cleanly (354 frames, 0 dropped).
Attribution
Requested by @roekatz / Roe Katz in Slack (message link).
Companion server telemetry PR: https://github.com/DecartAI/api-platform/pull/717
Note
High Risk
Default frame timing changes the on-wire format and subscribe-token contract; older viewers can connect but show black video, and worker creation failures now abort publisher/subscribe connects.
Overview
Browser realtime publishers now turn on glass-to-glass (G2G) latency automatically when LiveKit frame metadata and encoded transforms are available, instead of requiring
connect({ debugQuality: true }). The legacydebugQualityflag remains accepted but is deprecated and no longer gates measurement.Publisher path:
prepareBrowserConnectionprobes runtime support (isFrameMetadataRuntimeSupported), pre-creates a frame-metadata worker, and only then advertisesframe_timingto the server. Subscribe tokens can includeframe_timing: true. If the worker cannot be created after timing was requested, LiveKit connect fails permanently rather than joining without a strip transform (server packet trailers would break decode).Viewer path:
realtime.subscribedecodesframe_timingfrom the token, attaches the same worker toLiveKitRoomwhen required, and rejects unsupported environments (including React Native) withUNSUPPORTED_PLATFORM_FEATUREinstead of connecting to a black video.Measurement fix: G2G diagnostics treat LiveKit
timeSyncUpdatetimestamps as epoch ms (notperformance.timeOrigin-relative), fixing silent zero-sample measurement. Rolling latency uses a 300-sample window; deep preflight no longer passesdebugQuality.Docs and the SDK demo page drop the G2G opt-in UI and warn that viewers must upgrade before publishers on this SDK version.
Reviewed by Cursor Bugbot for commit f84959c. Bugbot is set up for automated code reviews on this repo. Configure here.