From 37e1965730a97b016690df6f048cc1b5f0573543 Mon Sep 17 00:00:00 2001 From: Joao Santos Date: Thu, 7 Aug 2025 12:16:32 -0300 Subject: [PATCH 01/11] disable fast negotiation --- packages/webrtc/src/RTCPeer.ts | 101 +++++++++++++++++---------------- 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/packages/webrtc/src/RTCPeer.ts b/packages/webrtc/src/RTCPeer.ts index 0adba5ec5..7bb868cd5 100644 --- a/packages/webrtc/src/RTCPeer.ts +++ b/packages/webrtc/src/RTCPeer.ts @@ -19,7 +19,7 @@ import { stopTrack, } from './utils' import { watchRTCPeerMediaPackets } from './utils/watchRTCPeerMediaPackets' -import { connectionPoolManager } from './connectionPoolManager' +// import { connectionPoolManager } from './connectionPoolManager' const RESUME_TIMEOUT = 12_000 export default class RTCPeer { @@ -562,32 +562,32 @@ export default class RTCPeer { private _setupRTCPeerConnection() { if (!this.instance) { // Try to get a pre-warmed connection from session-level pool - let pooledConnection: RTCPeerConnection | null = null - - try { - pooledConnection = connectionPoolManager.getConnection() - } catch (error) { - this.logger.debug('Could not access session connection pool', error) - } - - if (pooledConnection) { - this.logger.info( - 'Using pre-warmed connection from session pool with ICE candidates ready' - ) - this.instance = pooledConnection - - // The connection is already clean: - // - Mock tracks have been stopped and removed - // - ICE candidates are gathered and ready - // - TURN allocation is fresh - // - All event listeners have been removed - } else { - // Fallback to creating new connection - this.logger.debug( - 'Creating new RTCPeerConnection (no pooled connection available)' - ) + // let pooledConnection: RTCPeerConnection | null = null + + // try { + // pooledConnection = connectionPoolManager.getConnection() + // } catch (error) { + // this.logger.debug('Could not access session connection pool', error) + // } + + // if (pooledConnection) { + // this.logger.info( + // 'Using pre-warmed connection from session pool with ICE candidates ready' + // ) + // this.instance = pooledConnection + + // // The connection is already clean: + // // - Mock tracks have been stopped and removed + // // - ICE candidates are gathered and ready + // // - TURN allocation is fresh + // // - All event listeners have been removed + // } else { + // // Fallback to creating new connection + // this.logger.debug( + // 'Creating new RTCPeerConnection (no pooled connection available)' + // ) this.instance = RTCPeerConnection(this.config) - } + // } this._attachListeners() } @@ -1002,7 +1002,7 @@ export default class RTCPeer { } // Store all candidates - this._allCandidates.push(event.candidate) + // this._allCandidates.push(event.candidate) this.logger.debug('RTCPeer Candidate:', event.candidate) if (event.candidate.type === 'host') { @@ -1015,29 +1015,30 @@ export default class RTCPeer { this.instance.removeEventListener('icecandidate', this._onIce) this._onIceTimeout() }, this.options.maxIceGatheringTimeout) - } else { - /** - * With non-HOST candidate (srflx, prflx or relay), check if we have - * candidates for all media sections to support early invite - */ - if (this.instance.localDescription?.sdp) { - if (sdpHasValidCandidates(this.instance.localDescription.sdp)) { - // Take a snapshot of candidates at this point - if (this._candidatesSnapshot.length === 0 && this.type === 'offer') { - this._candidatesSnapshot = [...this._allCandidates] - this.logger.info( - 'SDP has candidates for all media sections, calling _sdpReady for early invite' - ) - setTimeout(() => this._sdpReady(), 0) // Defer to allow any pending operations to complete - } - } else { - this.logger.info( - 'SDP does not have candidates for all media sections, waiting for more candidates' - ) - this.logger.debug(this.instance.localDescription?.sdp) - } - } - } + } + // else { + // /** + // * With non-HOST candidate (srflx, prflx or relay), check if we have + // * candidates for all media sections to support early invite + // */ + // if (this.instance.localDescription?.sdp) { + // if (sdpHasValidCandidates(this.instance.localDescription.sdp)) { + // // Take a snapshot of candidates at this point + // if (this._candidatesSnapshot.length === 0 && this.type === 'offer') { + // this._candidatesSnapshot = [...this._allCandidates] + // this.logger.info( + // 'SDP has candidates for all media sections, calling _sdpReady for early invite' + // ) + // setTimeout(() => this._sdpReady(), 0) // Defer to allow any pending operations to complete + // } + // } else { + // this.logger.info( + // 'SDP does not have candidates for all media sections, waiting for more candidates' + // ) + // this.logger.debug(this.instance.localDescription?.sdp) + // } + // } + // } } private _retryWithMoreCandidates() { From e3808fc23979de55adb395d81403b557a6e9fcbc Mon Sep 17 00:00:00 2001 From: Joao Santos Date: Thu, 7 Aug 2025 12:45:40 -0300 Subject: [PATCH 02/11] enable just the pool --- packages/webrtc/src/RTCPeer.ts | 52 +++++++++++++++++----------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/webrtc/src/RTCPeer.ts b/packages/webrtc/src/RTCPeer.ts index 7bb868cd5..fc97ad9a2 100644 --- a/packages/webrtc/src/RTCPeer.ts +++ b/packages/webrtc/src/RTCPeer.ts @@ -19,7 +19,7 @@ import { stopTrack, } from './utils' import { watchRTCPeerMediaPackets } from './utils/watchRTCPeerMediaPackets' -// import { connectionPoolManager } from './connectionPoolManager' +import { connectionPoolManager } from './connectionPoolManager' const RESUME_TIMEOUT = 12_000 export default class RTCPeer { @@ -562,32 +562,32 @@ export default class RTCPeer { private _setupRTCPeerConnection() { if (!this.instance) { // Try to get a pre-warmed connection from session-level pool - // let pooledConnection: RTCPeerConnection | null = null - - // try { - // pooledConnection = connectionPoolManager.getConnection() - // } catch (error) { - // this.logger.debug('Could not access session connection pool', error) - // } - - // if (pooledConnection) { - // this.logger.info( - // 'Using pre-warmed connection from session pool with ICE candidates ready' - // ) - // this.instance = pooledConnection - - // // The connection is already clean: - // // - Mock tracks have been stopped and removed - // // - ICE candidates are gathered and ready - // // - TURN allocation is fresh - // // - All event listeners have been removed - // } else { - // // Fallback to creating new connection - // this.logger.debug( - // 'Creating new RTCPeerConnection (no pooled connection available)' - // ) + let pooledConnection: RTCPeerConnection | null = null + + try { + pooledConnection = connectionPoolManager.getConnection() + } catch (error) { + this.logger.debug('Could not access session connection pool', error) + } + + if (pooledConnection) { + this.logger.info( + 'Using pre-warmed connection from session pool with ICE candidates ready' + ) + this.instance = pooledConnection + + // The connection is already clean: + // - Mock tracks have been stopped and removed + // - ICE candidates are gathered and ready + // - TURN allocation is fresh + // - All event listeners have been removed + } else { + // Fallback to creating new connection + this.logger.debug( + 'Creating new RTCPeerConnection (no pooled connection available)' + ) this.instance = RTCPeerConnection(this.config) - // } + } this._attachListeners() } From 4351779d4b99eca298f3c2bc50b646fcc245c678 Mon Sep 17 00:00:00 2001 From: Joao Santos Date: Thu, 7 Aug 2025 13:00:23 -0300 Subject: [PATCH 03/11] enable early invites --- packages/webrtc/src/RTCPeer.ts | 46 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/webrtc/src/RTCPeer.ts b/packages/webrtc/src/RTCPeer.ts index fc97ad9a2..189123b81 100644 --- a/packages/webrtc/src/RTCPeer.ts +++ b/packages/webrtc/src/RTCPeer.ts @@ -1016,29 +1016,29 @@ export default class RTCPeer { this._onIceTimeout() }, this.options.maxIceGatheringTimeout) } - // else { - // /** - // * With non-HOST candidate (srflx, prflx or relay), check if we have - // * candidates for all media sections to support early invite - // */ - // if (this.instance.localDescription?.sdp) { - // if (sdpHasValidCandidates(this.instance.localDescription.sdp)) { - // // Take a snapshot of candidates at this point - // if (this._candidatesSnapshot.length === 0 && this.type === 'offer') { - // this._candidatesSnapshot = [...this._allCandidates] - // this.logger.info( - // 'SDP has candidates for all media sections, calling _sdpReady for early invite' - // ) - // setTimeout(() => this._sdpReady(), 0) // Defer to allow any pending operations to complete - // } - // } else { - // this.logger.info( - // 'SDP does not have candidates for all media sections, waiting for more candidates' - // ) - // this.logger.debug(this.instance.localDescription?.sdp) - // } - // } - // } + else { + /** + * With non-HOST candidate (srflx, prflx or relay), check if we have + * candidates for all media sections to support early invite + */ + if (this.instance.localDescription?.sdp) { + if (sdpHasValidCandidates(this.instance.localDescription.sdp)) { + // Take a snapshot of candidates at this point + if (this._candidatesSnapshot.length === 0 && this.type === 'offer') { + this._candidatesSnapshot = [...this._allCandidates] + this.logger.info( + 'SDP has candidates for all media sections, calling _sdpReady for early invite' + ) + setTimeout(() => this._sdpReady(), 0) // Defer to allow any pending operations to complete + } + } else { + this.logger.info( + 'SDP does not have candidates for all media sections, waiting for more candidates' + ) + this.logger.debug(this.instance.localDescription?.sdp) + } + } + } } private _retryWithMoreCandidates() { From 5be31a4418020f52a765554bed0ead7506765d79 Mon Sep 17 00:00:00 2001 From: Joao Santos Date: Thu, 7 Aug 2025 13:11:02 -0300 Subject: [PATCH 04/11] forgot this --- packages/webrtc/src/RTCPeer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webrtc/src/RTCPeer.ts b/packages/webrtc/src/RTCPeer.ts index 189123b81..4592f6477 100644 --- a/packages/webrtc/src/RTCPeer.ts +++ b/packages/webrtc/src/RTCPeer.ts @@ -1002,7 +1002,7 @@ export default class RTCPeer { } // Store all candidates - // this._allCandidates.push(event.candidate) + this._allCandidates.push(event.candidate) this.logger.debug('RTCPeer Candidate:', event.candidate) if (event.candidate.type === 'host') { From dc85a954083a2ca542a58408505a52dcca20b4c4 Mon Sep 17 00:00:00 2001 From: Joao Santos Date: Thu, 7 Aug 2025 15:18:38 -0300 Subject: [PATCH 05/11] skip early invites for renegotiations --- packages/webrtc/src/RTCPeer.ts | 5 +++-- packages/webrtc/src/RTCPeerConnectionManager.ts | 11 +++++++++++ packages/webrtc/src/connectionPoolManager.ts | 4 ++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/webrtc/src/RTCPeer.ts b/packages/webrtc/src/RTCPeer.ts index 4592f6477..face32cb7 100644 --- a/packages/webrtc/src/RTCPeer.ts +++ b/packages/webrtc/src/RTCPeer.ts @@ -1015,8 +1015,9 @@ export default class RTCPeer { this.instance.removeEventListener('icecandidate', this._onIce) this._onIceTimeout() }, this.options.maxIceGatheringTimeout) - } - else { + } + // avoid a early invite if we have pre gathered candidates + else if (!connectionPoolManager.hasPreGathereCandidates) { /** * With non-HOST candidate (srflx, prflx or relay), check if we have * candidates for all media sections to support early invite diff --git a/packages/webrtc/src/RTCPeerConnectionManager.ts b/packages/webrtc/src/RTCPeerConnectionManager.ts index 5d6a41a96..085f8ac92 100644 --- a/packages/webrtc/src/RTCPeerConnectionManager.ts +++ b/packages/webrtc/src/RTCPeerConnectionManager.ts @@ -30,6 +30,7 @@ export class RTCPeerConnectionManager { private forceRefresh: boolean private turnRefreshInterval: number = 240000 // 4 minutes private refreshTimer?: ReturnType + private candidatesCount = 0 private logger = getLogger() constructor(config: RTCConfiguration, poolSize = 3, forceRefresh = false) { @@ -110,6 +111,10 @@ export class RTCPeerConnectionManager { return null } + get hasPreGatheredCandidates() { + return this.candidatesCount > 0 + } + /** * Clean up the manager and all connections */ @@ -219,6 +224,12 @@ export class RTCPeerConnectionManager { pc.addEventListener('icegatheringstatechange', onGatheringComplete) + pc.addEventListener('icecandidate', (event) => { + if (event.candidate) { + this.candidatesCount++ + } + }) + // Timeout after 10 seconds const timer = setTimeout(() => { this.logger.warn('ICE gathering timeout, proceeding anyway') diff --git a/packages/webrtc/src/connectionPoolManager.ts b/packages/webrtc/src/connectionPoolManager.ts index 2d7ac513d..0d49b1e3e 100644 --- a/packages/webrtc/src/connectionPoolManager.ts +++ b/packages/webrtc/src/connectionPoolManager.ts @@ -4,6 +4,10 @@ import { RTCPeerConnectionManager } from './RTCPeerConnectionManager' class ConnectionPoolManagerSingleton { private manager?: RTCPeerConnectionManager private logger = getLogger() + + get hasPreGathereCandidates() { + return this.manager?.hasPreGatheredCandidates ?? false + } async initializePool( iceServers: RTCIceServer[], From 12b96f5bf0fdbff26607a86fffc9902eb7da84e2 Mon Sep 17 00:00:00 2001 From: Joao Santos Date: Mon, 11 Aug 2025 11:19:15 -0300 Subject: [PATCH 06/11] wait a negotiation to finish before sending a new offer --- packages/webrtc/src/BaseConnection.ts | 1 + packages/webrtc/src/RTCPeer.ts | 42 +++++++++++++++++---------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/packages/webrtc/src/BaseConnection.ts b/packages/webrtc/src/BaseConnection.ts index 8b0a3e604..53a80db55 100644 --- a/packages/webrtc/src/BaseConnection.ts +++ b/packages/webrtc/src/BaseConnection.ts @@ -1330,6 +1330,7 @@ export class BaseConnection< const peer = this.peer const negotiationPromise = new Promise((resolve, reject) => { peer._pendingNegotiationPromise = { + promise: negotiationPromise, resolve, reject, } diff --git a/packages/webrtc/src/RTCPeer.ts b/packages/webrtc/src/RTCPeer.ts index face32cb7..03a33cf48 100644 --- a/packages/webrtc/src/RTCPeer.ts +++ b/packages/webrtc/src/RTCPeer.ts @@ -38,6 +38,7 @@ export default class RTCPeer { private _candidatesSnapshot: RTCIceCandidate[] = [] private _allCandidates: RTCIceCandidate[] = [] private _processingLocalSDP = false + private _previousNegotiationPromise = Promise.resolve() /** * Both of these properties are used to have granular * control over when to `resolve` and when `reject` the @@ -52,6 +53,7 @@ export default class RTCPeer { * wait for the negotiation to complete. */ public _pendingNegotiationPromise?: { + promise: Promise resolve: (value?: unknown) => void reject: (error: unknown) => void } @@ -889,21 +891,6 @@ export default class RTCPeer { return } - // Check if we're still in the right state - if ( - this.type === 'offer' && - !['have-local-offer', 'have-local-pranswer'].includes( - this.instance.signalingState - ) - ) { - // the local SDP was processed already and onnegotiationneeded was not fired - // this happens because there are multiple places calling _sdpReady - this.logger.warn( - `_sdpReady called in wrong state: ${this.instance.signalingState}` - ) - return - } - this._processingLocalSDP = true clearTimeout(this._iceTimeout) @@ -927,6 +914,13 @@ export default class RTCPeer { } try { + const skipOnLocalSDPReady = await this._isAllowedToSendLocalSDP() + if (skipOnLocalSDPReady) { + this.logger.info('Skipping onLocalSDPReady due to early invite') + this._processingLocalSDP = false + return + } + await this.call.onLocalSDPReady(this) this._processingLocalSDP = false if (this.isAnswer) { @@ -940,6 +934,24 @@ export default class RTCPeer { } } + /** + * Waits for the pending negotiation promise to resolve + * and checks if the current signaling state allows to send a local SDP. + * This is used to prevent sending an offer when the signaling state is not appropriate. + * or when still waiting for a previous negotiation to complete. + */ + private async _isAllowedToSendLocalSDP() { + await this._pendingNegotiationPromise?.promise + + // Check if signalingState have the right state to sand an offer + return ( + this.type === 'offer' && + !['have-local-offer', 'have-local-pranswer'].includes( + this.instance.signalingState + ) + ) + } + private _sdpIsValid() { if (this.localSdp && this.hasIceServers) { return sdpHasValidCandidates(this.localSdp) From 69b971ab4a39004f34ac97ee147e611c6917d0d5 Mon Sep 17 00:00:00 2001 From: Joao Santos Date: Mon, 11 Aug 2025 11:21:04 -0300 Subject: [PATCH 07/11] cleanup --- packages/webrtc/src/RTCPeer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/webrtc/src/RTCPeer.ts b/packages/webrtc/src/RTCPeer.ts index 03a33cf48..2a2d9032f 100644 --- a/packages/webrtc/src/RTCPeer.ts +++ b/packages/webrtc/src/RTCPeer.ts @@ -38,7 +38,6 @@ export default class RTCPeer { private _candidatesSnapshot: RTCIceCandidate[] = [] private _allCandidates: RTCIceCandidate[] = [] private _processingLocalSDP = false - private _previousNegotiationPromise = Promise.resolve() /** * Both of these properties are used to have granular * control over when to `resolve` and when `reject` the From e7aa9dd0df3b93b21e89be1238a175af68359696 Mon Sep 17 00:00:00 2001 From: Joao Santos Date: Mon, 11 Aug 2025 13:15:43 -0300 Subject: [PATCH 08/11] fix --- packages/webrtc/src/BaseConnection.ts | 9 ++++++++- packages/webrtc/src/RTCPeer.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/webrtc/src/BaseConnection.ts b/packages/webrtc/src/BaseConnection.ts index 53a80db55..45efb0612 100644 --- a/packages/webrtc/src/BaseConnection.ts +++ b/packages/webrtc/src/BaseConnection.ts @@ -1245,6 +1245,13 @@ export class BaseConnection< const rtcPeer = new RTCPeer(this, type) this.appendRTCPeer(rtcPeer) this.runRTCPeerWorkers(rtcPeer.uuid) + const negotiationPromise = new Promise((resolve, reject) => { + rtcPeer._pendingNegotiationPromise = { + resolve, + reject, + } + }) + rtcPeer._pendingNegotiationPromise!.promise = negotiationPromise return rtcPeer } @@ -1330,11 +1337,11 @@ export class BaseConnection< const peer = this.peer const negotiationPromise = new Promise((resolve, reject) => { peer._pendingNegotiationPromise = { - promise: negotiationPromise, resolve, reject, } }) + peer._pendingNegotiationPromise!.promise = negotiationPromise const shouldEnableAudio = ['sendonly', 'sendrecv'].includes( audio?.direction || '' diff --git a/packages/webrtc/src/RTCPeer.ts b/packages/webrtc/src/RTCPeer.ts index 2a2d9032f..dcbbd7d5b 100644 --- a/packages/webrtc/src/RTCPeer.ts +++ b/packages/webrtc/src/RTCPeer.ts @@ -52,7 +52,7 @@ export default class RTCPeer { * wait for the negotiation to complete. */ public _pendingNegotiationPromise?: { - promise: Promise + promise?: Promise resolve: (value?: unknown) => void reject: (error: unknown) => void } From 39b0f39ef96ed1ae566da6c28ad0726c09c071ae Mon Sep 17 00:00:00 2001 From: Joao Santos Date: Mon, 11 Aug 2025 14:27:20 -0300 Subject: [PATCH 09/11] negotiation completer --- packages/webrtc/src/BaseConnection.ts | 10 +----- packages/webrtc/src/RTCPeer.ts | 47 ++++++++++++++++----------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/packages/webrtc/src/BaseConnection.ts b/packages/webrtc/src/BaseConnection.ts index 45efb0612..96c255cdf 100644 --- a/packages/webrtc/src/BaseConnection.ts +++ b/packages/webrtc/src/BaseConnection.ts @@ -1245,14 +1245,7 @@ export class BaseConnection< const rtcPeer = new RTCPeer(this, type) this.appendRTCPeer(rtcPeer) this.runRTCPeerWorkers(rtcPeer.uuid) - const negotiationPromise = new Promise((resolve, reject) => { - rtcPeer._pendingNegotiationPromise = { - resolve, - reject, - } - }) - rtcPeer._pendingNegotiationPromise!.promise = negotiationPromise - + return rtcPeer } @@ -1341,7 +1334,6 @@ export class BaseConnection< reject, } }) - peer._pendingNegotiationPromise!.promise = negotiationPromise const shouldEnableAudio = ['sendonly', 'sendrecv'].includes( audio?.direction || '' diff --git a/packages/webrtc/src/RTCPeer.ts b/packages/webrtc/src/RTCPeer.ts index dcbbd7d5b..7bb0c9e9c 100644 --- a/packages/webrtc/src/RTCPeer.ts +++ b/packages/webrtc/src/RTCPeer.ts @@ -38,6 +38,8 @@ export default class RTCPeer { private _candidatesSnapshot: RTCIceCandidate[] = [] private _allCandidates: RTCIceCandidate[] = [] private _processingLocalSDP = false + private _waitNegotiation: Promise = Promise.resolve() + private _waitNegotiationCompleter: () => void /** * Both of these properties are used to have granular * control over when to `resolve` and when `reject` the @@ -52,7 +54,6 @@ export default class RTCPeer { * wait for the negotiation to complete. */ public _pendingNegotiationPromise?: { - promise?: Promise resolve: (value?: unknown) => void reject: (error: unknown) => void } @@ -198,6 +199,18 @@ export default class RTCPeer { return false } + private _negotiationCompleted(error?: unknown) { + if (!error) { + this._resolveStartMethod() + this._waitNegotiationCompleter?.() + this._pendingNegotiationPromise?.resolve() + } else { + this._rejectStartMethod(error) + this._waitNegotiationCompleter?.() + this._pendingNegotiationPromise?.reject(error) + } + } + stopTrackSender(kind: string) { try { const sender = this._getSenderByKind(kind) @@ -327,8 +340,7 @@ export default class RTCPeer { this.restartIce() } catch (error) { this.logger.error('restartIceWithRelayOnly', error) - this._rejectStartMethod?.(error) - this._pendingNegotiationPromise?.reject(error) + this._negotiationCompleted(error) } } @@ -502,15 +514,14 @@ export default class RTCPeer { } } catch (error) { this.logger.error(`Error creating ${this.type}:`, error) - this._pendingNegotiationPromise?.reject(error) + this._negotiationCompleted(error) } } onRemoteBye({ code, message }: { code: string; message: string }) { // It could be a negotiation/signaling error so reject the "startMethod" const error = { code, message } - this._rejectStartMethod?.(error) - this._pendingNegotiationPromise?.reject(error) + this._negotiationCompleted(error) this.stop() } @@ -543,8 +554,7 @@ export default class RTCPeer { * we need to reply to the server and wait for the signaling. */ if (this.isOffer) { - this._resolveStartMethod() - this._pendingNegotiationPromise?.resolve() + this._negotiationCompleted() } this.resetNeedResume() @@ -555,8 +565,7 @@ export default class RTCPeer { error ) this.call.hangup() - this._rejectStartMethod(error) - this._pendingNegotiationPromise?.reject(error) + this._negotiationCompleted(error) } } @@ -602,8 +611,7 @@ export default class RTCPeer { try { this._localStream = await this._retrieveLocalStream() } catch (error) { - this._rejectStartMethod(error) - this._pendingNegotiationPromise?.reject(error) + this._negotiationCompleted(error) return this.call.setState('hangup') } @@ -920,15 +928,17 @@ export default class RTCPeer { return } + this._waitNegotiation = new Promise((resolve) => { + this._waitNegotiationCompleter = resolve + }) + await this.call.onLocalSDPReady(this) this._processingLocalSDP = false if (this.isAnswer) { - this._resolveStartMethod() - this._pendingNegotiationPromise?.resolve() + this._negotiationCompleted() } } catch (error) { - this._rejectStartMethod(error) - this._pendingNegotiationPromise?.reject(error) + this._negotiationCompleted(error) this._processingLocalSDP = false } } @@ -940,7 +950,7 @@ export default class RTCPeer { * or when still waiting for a previous negotiation to complete. */ private async _isAllowedToSendLocalSDP() { - await this._pendingNegotiationPromise?.promise + await this._waitNegotiation // Check if signalingState have the right state to sand an offer return ( @@ -978,8 +988,7 @@ export default class RTCPeer { code: 'ICE_GATHERING_FAILED', message: 'Ice gathering timeout', } - this._rejectStartMethod(error) - this._pendingNegotiationPromise?.reject(error) + this._negotiationCompleted(error) this.call.setState('destroy') return } From b831537feeb98c0391bb0e914ff16cbef54848c6 Mon Sep 17 00:00:00 2001 From: Joao Santos Date: Mon, 11 Aug 2025 15:13:35 -0300 Subject: [PATCH 10/11] remove candidates count --- packages/webrtc/src/RTCPeer.ts | 4 +--- packages/webrtc/src/RTCPeerConnectionManager.ts | 10 ---------- packages/webrtc/src/connectionPoolManager.ts | 3 --- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/packages/webrtc/src/RTCPeer.ts b/packages/webrtc/src/RTCPeer.ts index 7bb0c9e9c..b462dcba8 100644 --- a/packages/webrtc/src/RTCPeer.ts +++ b/packages/webrtc/src/RTCPeer.ts @@ -1035,9 +1035,7 @@ export default class RTCPeer { this.instance.removeEventListener('icecandidate', this._onIce) this._onIceTimeout() }, this.options.maxIceGatheringTimeout) - } - // avoid a early invite if we have pre gathered candidates - else if (!connectionPoolManager.hasPreGathereCandidates) { + } else { /** * With non-HOST candidate (srflx, prflx or relay), check if we have * candidates for all media sections to support early invite diff --git a/packages/webrtc/src/RTCPeerConnectionManager.ts b/packages/webrtc/src/RTCPeerConnectionManager.ts index 085f8ac92..6995fbf1c 100644 --- a/packages/webrtc/src/RTCPeerConnectionManager.ts +++ b/packages/webrtc/src/RTCPeerConnectionManager.ts @@ -30,7 +30,6 @@ export class RTCPeerConnectionManager { private forceRefresh: boolean private turnRefreshInterval: number = 240000 // 4 minutes private refreshTimer?: ReturnType - private candidatesCount = 0 private logger = getLogger() constructor(config: RTCConfiguration, poolSize = 3, forceRefresh = false) { @@ -111,9 +110,6 @@ export class RTCPeerConnectionManager { return null } - get hasPreGatheredCandidates() { - return this.candidatesCount > 0 - } /** * Clean up the manager and all connections @@ -224,12 +220,6 @@ export class RTCPeerConnectionManager { pc.addEventListener('icegatheringstatechange', onGatheringComplete) - pc.addEventListener('icecandidate', (event) => { - if (event.candidate) { - this.candidatesCount++ - } - }) - // Timeout after 10 seconds const timer = setTimeout(() => { this.logger.warn('ICE gathering timeout, proceeding anyway') diff --git a/packages/webrtc/src/connectionPoolManager.ts b/packages/webrtc/src/connectionPoolManager.ts index 0d49b1e3e..db69a2cb2 100644 --- a/packages/webrtc/src/connectionPoolManager.ts +++ b/packages/webrtc/src/connectionPoolManager.ts @@ -5,9 +5,6 @@ class ConnectionPoolManagerSingleton { private manager?: RTCPeerConnectionManager private logger = getLogger() - get hasPreGathereCandidates() { - return this.manager?.hasPreGatheredCandidates ?? false - } async initializePool( iceServers: RTCIceServer[], From 6b2744f366b6f7623a7d3a0aa5f9bcdbba31d176 Mon Sep 17 00:00:00 2001 From: Joao Santos Date: Wed, 13 Aug 2025 12:34:41 -0300 Subject: [PATCH 11/11] bug fix --- packages/webrtc/src/RTCPeer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webrtc/src/RTCPeer.ts b/packages/webrtc/src/RTCPeer.ts index b462dcba8..c24e9c81b 100644 --- a/packages/webrtc/src/RTCPeer.ts +++ b/packages/webrtc/src/RTCPeer.ts @@ -955,7 +955,7 @@ export default class RTCPeer { // Check if signalingState have the right state to sand an offer return ( this.type === 'offer' && - !['have-local-offer', 'have-local-pranswer'].includes( + ['have-local-offer', 'have-local-pranswer'].includes( this.instance.signalingState ) )