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,
|
Reverts #191 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes default realtime wire format and viewer compatibility semantics; publishers on the reverted behavior no longer send frame trailers unless `debugQuality` is set, which fixes subscriber breakage but is a behavioral/API contract shift integrators should verify. > > **Overview** > Reverts default-on browser glass-to-glass (G2G) measurement so frame metadata and packet trailers are only used when publishers explicitly opt in. > > **Publishers** must pass `connect({ debugQuality: true })` to enable LiveKit frame-metadata workers, advertise `frame_timing` to the server, and populate `g2gMs` / `ttffMs` on `stats` and connection quality. Without that flag, sessions behave like standard realtime connects with no frame trailers. Worker creation failures no longer abort the media connection—they log and continue without latency metrics. Deep connectivity preflight still turns on `debugQuality` for its short probe session. > > **Viewers** no longer read `frame_timing` from subscribe tokens or spin up strip workers; `subscribe` uses a plain LiveKit room, so mixed SDK versions and React Native subscribers are not blocked or left with a black video when an upgraded publisher would have advertised timing. > > Docs and the SDK demo page describe G2G as opt-in diagnostic measurement and add a checkbox wired to `debugQuality`. React Native rejects `debugQuality` like other browser-only features. G2G playout timestamp handling is corrected to treat LiveKit `timeSyncUpdate` timestamps as relative to `performance.timeOrigin`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2de0f09. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
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).
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.