Skip to content

Commit 13c96e0

Browse files
authored
Send initial media sections with v1 signalling for FF (#1963)
1 parent 6bfc91c commit 13c96e0

2 files changed

Lines changed: 38 additions & 4 deletions

File tree

.changeset/ten-dryers-stare.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"livekit-client": patch
3+
---
4+
5+
Send initial media sections with v1 signalling for FF

src/room/RTCEngine.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,12 +324,21 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
324324
this.joinAttempts += 1;
325325

326326
this.setupSignalClientCallbacks();
327+
// Whether the initial publisher offer is bundled with the join request. Computed once and
328+
// reused after the join below. Only the (non-Firefox) offer-with-join path does this.
329+
const sendOfferWithJoin = !useV0Path && isPublisherOfferWithJoinSupported();
330+
327331
let offerProto: SessionDescription | undefined;
328-
if (!useV0Path && isPublisherOfferWithJoinSupported()) {
332+
if (sendOfferWithJoin) {
329333
if (!this.pcManager) {
334+
// Firefox is excluded from offer-with-join (see isPublisherOfferWithJoinSupported):
335+
// customers reported ICE connectivity problems for FF on this path (#1919) that we were
336+
// never able to reproduce, so out of caution FF stays on the deferred path below. The
337+
// exact cause is unknown — note that ICE gathering does not actually start here, since
338+
// createInitialOffer() defers setLocalDescription (via pendingInitialOffer) until the
339+
// answer is applied, after updateConfiguration() has set the server's TURN servers.
330340
await this.configure();
331-
this.createDataChannels();
332-
this.addMediaSections(initialMediaSectionsAudio, initialMediaSectionsVideo);
341+
this.applyInitialPublisherLayout();
333342
}
334343
const offer = await this.pcManager?.publisher.createInitialOffer();
335344
if (offer) {
@@ -358,11 +367,20 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
358367
this.participantSid = joinResponse.participant?.sid;
359368

360369
this.subscriberPrimary = joinResponse.subscriberPrimary;
361-
if (!useV0Path && isPublisherOfferWithJoinSupported()) {
370+
if (sendOfferWithJoin) {
362371
this.pcManager?.updateConfiguration(this.makeRTCConfiguration(joinResponse));
363372
} else {
364373
if (!this.pcManager) {
374+
// Deferred path (Firefox, and V0): configure with the join response so the PC picks up
375+
// the server's ICE servers and topology, then negotiate separately rather than bundling
376+
// the offer with the join.
365377
await this.configure(joinResponse, !useV0Path);
378+
if (!useV0Path) {
379+
// The V1 first offer must carry the media layout so Firefox binds receive decoders for
380+
// subscribed tracks — without it, subscribed audio/video arrive as RTP but
381+
// never decode. V0 (legacy dual-PC) keeps its original lazy behavior.
382+
this.applyInitialPublisherLayout();
383+
}
366384
}
367385
// create offer
368386
if (!this.subscriberPrimary || joinResponse.fastPublish) {
@@ -814,6 +832,17 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
814832
return rtcConfig;
815833
}
816834

835+
/**
836+
* Populate the publisher PC so its first offer carries the data channels + recvonly media
837+
* sections. Required for every V1 connection: Firefox only binds receive decoders for media
838+
* present in that first offer, and the offer-with-join path needs the sections to
839+
* build a meaningful initial offer. Must be called on a configured pcManager.
840+
*/
841+
private applyInitialPublisherLayout() {
842+
this.createDataChannels();
843+
this.addMediaSections(initialMediaSectionsAudio, initialMediaSectionsVideo);
844+
}
845+
817846
private addMediaSections(numAudios: number, numVideos: number) {
818847
const transceiverInit: RTCRtpTransceiverInit = { direction: 'recvonly' };
819848
for (let i: number = 0; i < numAudios; i++) {

0 commit comments

Comments
 (0)