Skip to content

fix(client): allow forcing the dual peer connection path on WebRTC - #963

Open
chinmayv095 wants to merge 1 commit into
elevenlabs:mainfrom
chinmayv095:fix/webrtc-single-peer-connection-option
Open

fix(client): allow forcing the dual peer connection path on WebRTC#963
chinmayv095 wants to merge 1 commit into
elevenlabs:mainfrom
chinmayv095:fix/webrtc-single-peer-connection-option

Conversation

@chinmayv095

Copy link
Copy Markdown
Contributor

Fixes #960

What happens

WebRTCConnection.create() constructs the LiveKit Room with no options, so singlePeerConnection takes livekit-client's own default, which is true as of 2.21.0:

// livekit-client 2.21.0, options.d.ts
/**
 * will attempt to connect via single peer connection mode.
 * falls back to dual peer connection mode if not available.
 *
 * @default true
 */
singlePeerConnection: boolean;

The v1 join protocol bundles the publisher offer in the JoinRequest, which moves the point in the connection sequence where the microphone is requested. create() enables the microphone off RoomEvent.SignalConnected, so the timing of that request follows the protocol version:

room.once(RoomEvent.SignalConnected, () => {
  room.localParticipant.setMicrophoneEnabled(true)...
});

@sailod reports that on iOS and iPadOS Safari the request is rejected at that earlier point with NotAllowedError, and that pinning singlePeerConnection: false through patch-package restores it.

Why this is not a straight revert

singlePeerConnection: false did exist. It was added in ae50508 (#782) as defense in depth for LiveKit servers that did not process a bundled publisher offer, and removed in c037e48 (#780). The removal was deliberate, not incidental: that commit message calls it out by name as an intended edit that a rebase had dropped and that was being restored.

So this PR does not restore false as the client default. Whether the server side is now uniformly ready for v1 is a call I cannot make from outside the deployment, and re-pinning every caller to v0 would undo #780 for everyone in order to fix one platform.

What it does instead

Adds webRtc.singlePeerConnection, following the shape #934 landed three days ago. That PR started as a general rtcConfig passthrough and was narrowed before merge to a single named option under webRtc, so this reuses the same namespace and the same one-option-at-a-time convention rather than reopening the passthrough question.

const singlePeerConnection = config.webRtc?.singlePeerConnection;
const room = new Room(
  singlePeerConnection === undefined ? undefined : { singlePeerConnection }
);

Left undefined when unset, so livekit-client's default continues to apply and nothing changes for existing callers. The reporter can drop patch-package and pass webRtc: { singlePeerConnection: false } instead.

Considered and not taken

Defaulting to false on iOS and iPadOS specifically. That means user agent sniffing in a package that deliberately keeps DOM assumptions out of common code, and it would encode a guess about which platforms are affected. If you would rather ship a platform default, or flip the client default back to false outright, both are small follow-ups on top of this and I am happy to send either.

Verification

Four tests in WebRTCConnection.test.ts, placed beside the existing iceTransportPolicy test rather than at the end of the file, since my open #953 appends there.

Test Asserts
forces the dual peer connection path when set to false false reaches the Room constructor
passes the option through when set to true true does too, so the option is not hard-coded one way
leaves the LiveKit default alone when the option is omitted no mode is pinned when unset
does not pin the peer connection mode when only iceTransportPolicy is set the two webRtc options stay independent

Fail-first, stashing only WebRTCConnection.ts: 2 failed, 17 passed. The two failures are the two that assert the new behaviour. The other two pass both ways by design, which is the point of them: they are the back-compat controls, and a fix that pinned a default would break them.

They assert that no singlePeerConnection was pinned rather than asserting the exact Room call arity, because new Room() and new Room(undefined) are the same thing to LiveKit and pinning the arity would fail on any harmless refactor.

livekit-client defaults singlePeerConnection to true, so the v1 join
protocol bundles the publisher offer in the JoinRequest and moves the
microphone request earlier in the connection sequence. iOS and iPadOS
Safari reject it there.

Add a webRtc.singlePeerConnection session option so callers can opt back
into the dual peer connection path, following the shape elevenlabs#934 landed for
webRtc.iceTransportPolicy. Left undefined when unset, so livekit-client's
own default continues to apply.

Fixes elevenlabs#960
@cursor

cursor Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

PR Summary

Low Risk
Opt-in session flag with backward-compatible default; limited to LiveKit Room construction and covered by targeted tests.

Overview
Adds webRtc.singlePeerConnection on session config so callers can opt into LiveKit’s dual peer connection path (false) when the v1 single-connection join moves mic permission too early (e.g. iOS/iPadOS Safari NotAllowedError). WebRTCConnection.create now passes that flag into new Room(...) only when set; omitting it leaves livekit-client’s default unchanged.

The option is documented on BaseSessionConfig, shipped as a minor changeset for @elevenlabs/client, and covered by four unit tests (true/false passthrough, unset default, independence from iceTransportPolicy).

Reviewed by Cursor Bugbot for commit 7307f67. Bugbot is set up for automated code reviews on this repo. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant