Skip to content

Scale the video start bitrate hint by connection setup time - #2146

Open
changt wants to merge 1 commit into
mainfrom
sen/adaptive-start-bitrate
Open

changt wants to merge 1 commit into
mainfrom
sen/adaptive-start-bitrate

Conversation

@changt

@changt changt commented Oct 9, 2026

Copy link
Copy Markdown

Linear: CLT-3380
Rust counterparts: livekit/rust-sdks#1467, livekit/rust-sdks#1505
Android counterparts: livekit/client-sdk-android#1031, livekit/client-sdk-android#1036
Flutter counterpart: livekit/client-sdk-flutter#1228

Builds on #2102, which made the x-google-start-bitrate hint a connection-level value written once per peer connection.

Problem

For camera tracks, the hint is a fixed min(90% of target, 1 Mbps), chosen with no information about the network. On a constrained uplink, 1 Mbps overshoots. The estimator then has to recover, which shows up as a freeze and a retransmit storm over the first 5–15 s. On a good network 1 Mbps is the right value. libwebrtc cannot probe the path before a video sender exists, so connection setup time is the only network signal available before the first video offer.

Change

Same formula as Rust and Android:

cap_kbps = clamp(1000 − (setup_ms − 1500) × 700 / 2000, 300, 1000)
  • ≤ 1.5 s → 1000 kbps, so healthy networks are unchanged. 1.5–3.5 s → linear ramp. ≥ 3.5 s → 300 kbps, libwebrtc's own default starting estimate.
  • The existing 90%-of-target rule still applies on top, and the hint is still written at the floor rather than skipped.
  • Screen share is exempt from the ramp and stays uncapped, as in Keep screen share uncapped by the start bitrate ramp rust-sdks#1505.
  • RTCEngine times each join() attempt, so a join retry or a full restart is timed from scratch. It hands the setup time to the publisher PCTransport the first time the primary transport reports connected. Resumes and ICE restarts never restart the timer and keep the estimator they have. The write-once latch from Write the video start bitrate hint as one connection-level value, once #2102 is unchanged.

Publishing during connect

Unlike Rust, JS lets an app publish before the peer connection is up. publishTrack only waits for the signal connection, and the demo does exactly this to shorten time-to-publish. In that case the first video offer is built before the setup time is known. Holding the offer until the connection is up would add latency, so the offer uses the time elapsed since the join started instead. Real setup time can only be longer, so this never seeds higher than the previous fixed 1 Mbps cap. It is a partial ramp, though: a connection that takes 3.6 s to set up but builds its offer 2.5 s in gets about 650 kbps rather than the floor. With no timing at all, the cap stays at 1 Mbps.

The existing applied x-google-start-bitrate log line reports connectionSetupTimeMs, which is the measured setup time or the elapsed estimate, whichever was used. A separate connection setup took N ms line is logged when the transport connects.

Testing

  • PCTransport.test.ts: the ramp at the measured setup times and anchors (same cases as Rust), the 1 Mbps fallback without a setup time, 90% of target under the ramp, screen share uncapped at every setup time, the connection-level value, and the elapsed-time estimate before the setup time is measured. npx vitest run src: 870 passed.
  • Ran examples/demo against a LiveKit Cloud development token server with a fake camera in Chrome, on an unshaped network: setup about 400 ms, offer carried x-google-start-bitrate=1000, and the estimate opened at about 1.8 Mbps.
  • Not yet measured under shaped networks. For Rust measurements, see Scale the video start bitrate hint by connection setup time rust-sdks#1467.

Port of livekit/rust-sdks#1467 and #1505 (CLT-3380). The camera
x-google-start-bitrate cap is 1 Mbps for connections that set up within
1.5 s and ramps linearly down to 300 kbps at 3.5 s or slower. Screen share
stays uncapped.

RTCEngine times each join attempt and hands the setup time to the
publisher when the primary transport first connects. A video published
during connect is seeded from the time elapsed so far rather than
delaying its offer.
@changeset-bot

changeset-bot Bot commented Oct 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a78655d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
livekit-client Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Devin Review: 1 flag

Not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
dist/livekit-client.esm.mjs 112.8 KB (+0.24% 🔺)
dist/livekit-client.umd.js 121.94 KB (+0.16% 🔺)

This branch has not been deployed

No deployments
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