Skip to content

Create data channels as negotiated in single PC mode (protocol 18) - #2160

Draft
lukasIO wants to merge 2 commits into
mainfrom
feat/negotiated-data-channels
Draft

lukasIO wants to merge 2 commits into
mainfrom
feat/negotiated-data-channels

Conversation

@lukasIO

@lukasIO lukasIO commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

In single peer connection mode, the server cannot send data until the client's data channels finish the in-band open handshake. The client creates _reliable, _lossy and _data_track as in-band channels. Each one opens with a DCEP OPEN message from the client. The server must wait for that message before it can send on the channel. That costs about one round trip of server-to-client readiness on every connect.

This change creates the three publisher channels as negotiated, with fixed SCTP stream IDs 100, 101 and 102. The server uses the same IDs (livekit/livekit#5017, livekit/protocol#1871), so both sides can send right after SCTP connects. The client protocol goes to 18. The client uses negotiated channels only in single PC mode against a server that reports protocol 18 or higher. Dual PC keeps in-band channels, because there the server opens its own channels. This is a port of livekit/rust-sdks#1527.

The failure case is an older server on the offer-with-join path. There the client builds its first offer before it knows the server protocol. It creates negotiated channels optimistically. If the join response reports protocol 17 or lower, the client closes them and opens in-band channels with the same labels. The SCTP section is already in the offer, so this needs no renegotiation. The Firefox deferred path creates the channels after the join response, so it picks the right kind directly.

Impact

The saving is one DCEP round trip before the server can send on a fresh connection. It is not measured yet.

The IDs come from the NegotiatedDataChannelID enum, so this also bumps @livekit/protocol to 1.53.0. Against servers below protocol 18, the channels stay in-band and behavior does not change.

🤖 Generated with Claude Code

Port of livekit/rust-sdks#1527. Bumps the client protocol to 18 and
creates the publisher data channels with fixed SCTP stream IDs
(100/101/102) when the server supports it, falling back to in-band
channels against older servers or in dual PC mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fcb7d70

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 Minor

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

@lukasIO
lukasIO marked this pull request as draft October 19, 2026 12:35

@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 found 1 potential issue.

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

Devin Review

Comment thread src/room/RTCEngine.ts
Comment on lines +949 to +953
private useNegotiatedDataChannels() {
return (
this.pcManager?.mode === 'publisher-only' &&
(this.latestJoinResponse?.serverInfo?.protocol ?? 0) >= PROTOCOL_NEGOTIATED_DATA_CHANNELS
);

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.

🔴 Publisher data stops after server downgrade

When a session resumes on an older server, useNegotiatedDataChannels changes its verdict but leaves the negotiated channels open. The older server never receives their DCEP OPEN, so published data stops reaching it.

Learn more

A single-PC session with negotiated channels keeps its peer connection during signal resume. The resume response replaces latestJoinResponse.serverInfo, but channel recreation only runs for an open channel with a null ID in resumeConnection. Negotiated channels have fixed non-null IDs, so a resume onto a pre-18 server leaves them unchanged. That server does not use the fixed negotiated IDs and receives no in-band channel open message, while the client continues sending through the old channels.

Example: A client joins a protocol-18 server using negotiated ID 100 for reliable data. Its signaling session resumes on a protocol-17 server. The response updates the protocol to 17, but the client continues sending reliable packets on ID 100 instead of opening an in-band channel.

Recommended fix: On resume, compare the active publisher channel mode with the new server's protocol before completing reconnection. If it changed, replace the channels with the appropriate type and wait for the replacement to open; handle queued reliable messages and sync-state channel IDs during this transition.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

a session resume is not expected to land on an older version.

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
dist/livekit-client.esm.mjs 116.04 KB (+0.24% 🔺)
dist/livekit-client.umd.js 125.33 KB (+0.22% 🔺)
dist/core/core.mjs 78.77 KB (+0.28% 🔺)

Bump @livekit/protocol to 1.53.0, which defines NegotiatedDataChannelID,
and drop the local ID constants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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