From cd78a16c6637ee1e45d0a11e1b106b6ae9e301ad Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Fri, 14 Nov 2025 19:01:49 +0100 Subject: [PATCH] Improve trickle ICE with early candidate buffering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Buffer ICE candidates generated before offerId is set - Flush buffered candidates immediately after offerId is set - Continue sending candidates as they arrive (true trickle ICE) - Prevents losing early ICE candidates during setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/connection.ts | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/connection.ts b/src/connection.ts index a47ebae..79cfbe7 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -60,6 +60,7 @@ export class RondevuConnection { private lastIceTimestamp: number = Date.now(); private eventListeners: Map> = new Map(); private dataChannel?: RTCDataChannel; + private pendingIceCandidates: string[] = []; /** * Current connection state @@ -101,11 +102,19 @@ export class RondevuConnection { */ private setupPeerConnection(): void { this.pc.onicecandidate = async (event) => { - if (event.candidate && this.offerId) { - try { - await this.offersApi.addIceCandidates(this.offerId, [event.candidate.candidate]); - } catch (err) { - console.error('Error sending ICE candidate:', err); + if (event.candidate) { + const candidateString = event.candidate.candidate; + + if (this.offerId) { + // offerId is set, send immediately (trickle ICE) + try { + await this.offersApi.addIceCandidates(this.offerId, [candidateString]); + } catch (err) { + console.error('Error sending ICE candidate:', err); + } + } else { + // offerId not set yet, buffer the candidate + this.pendingIceCandidates.push(candidateString); } } }; @@ -141,6 +150,20 @@ export class RondevuConnection { }; } + /** + * Flush buffered ICE candidates (trickle ICE support) + */ + private async flushPendingIceCandidates(): Promise { + if (this.pendingIceCandidates.length > 0 && this.offerId) { + try { + await this.offersApi.addIceCandidates(this.offerId, this.pendingIceCandidates); + this.pendingIceCandidates = []; + } catch (err) { + console.error('Error flushing pending ICE candidates:', err); + } + } + } + /** * Create an offer and advertise on topics */ @@ -168,6 +191,9 @@ export class RondevuConnection { this.offerId = offers[0].id; + // Flush any ICE candidates that were generated during offer creation + await this.flushPendingIceCandidates(); + // Start polling for answers this.startAnswerPolling(); @@ -198,6 +224,9 @@ export class RondevuConnection { // This prevents a race condition where ICE candidates arrive before answer is registered this.offerId = offerId; + // Flush any ICE candidates that were generated during answer creation + await this.flushPendingIceCandidates(); + // Start polling for ICE candidates this.startIcePolling(); }