From 9658b75a10e2773eaacb4ffae165cf04307da966 Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Tue, 29 Apr 2025 10:52:07 -0300 Subject: [PATCH 1/5] Configuring the voice-agent example to use ice-servers and wait for all the ice candidates. --- examples/p2p-webrtc/voice-agent/index.html | 38 +++++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/examples/p2p-webrtc/voice-agent/index.html b/examples/p2p-webrtc/voice-agent/index.html index 0692f5b7c..4ee386627 100644 --- a/examples/p2p-webrtc/voice-agent/index.html +++ b/examples/p2p-webrtc/voice-agent/index.html @@ -24,27 +24,44 @@ let connected = false let peerConnection = null - /*const waitForIceGatheringComplete = async (pc) => { + const waitForIceGatheringComplete = async (pc, timeoutMs = 5000) => { if (pc.iceGatheringState === 'complete') return; + console.log("Waiting for ICE gathering to complete..."); return new Promise((resolve) => { + let timeoutId; const checkState = () => { + console.log("icegatheringstatechange:", pc.iceGatheringState); if (pc.iceGatheringState === 'complete') { - pc.removeEventListener('icegatheringstatechange', checkState); + cleanup(); resolve(); } }; + const onTimeout = () => { + console.warn(`ICE gathering timed out after ${timeoutMs} ms.`); + cleanup(); + resolve(); + }; + const cleanup = () => { + pc.removeEventListener('icegatheringstatechange', checkState); + clearTimeout(timeoutId); + }; pc.addEventListener('icegatheringstatechange', checkState); + timeoutId = setTimeout(onTimeout, timeoutMs); }); - }*/ + }; + const createSmallWebRTCConnection = async (audioTrack) => { - const pc = new RTCPeerConnection() + const config = { + iceServers: [{ urls: ["stun:stun.l.google.com:19302"] }], + }; + const pc = new RTCPeerConnection(config) pc.ontrack = e => audioEl.srcObject = e.streams[0] // SmallWebRTCTransport expects to receive both transceivers pc.addTransceiver(audioTrack, { direction: 'sendrecv' }) pc.addTransceiver('video', { direction: 'sendrecv' }) await pc.setLocalDescription(await pc.createOffer()) - //await waitForIceGatheringComplete(pc) + await waitForIceGatheringComplete(pc) const offer = pc.localDescription const response = await fetch('/api/offer', { body: JSON.stringify({ sdp: offer.sdp, type: offer.type}), @@ -57,9 +74,14 @@ } const connect = async () => { + _onConnecting() const audioStream = await navigator.mediaDevices.getUserMedia({audio: true}) peerConnection= await createSmallWebRTCConnection(audioStream.getAudioTracks()[0]) + peerConnection.oniceconnectionstatechange = () => { + console.log("oniceconnectionstatechange", peerConnection?.iceConnectionState) + } peerConnection.onconnectionstatechange = () => { + console.log("onconnectionstatechange", peerConnection?.connectionState) let connectionState = peerConnection?.connectionState if (connectionState === 'connected') { _onConnected() @@ -69,6 +91,12 @@ } } + const _onConnecting = () => { + statusEl.textContent = "Connecting" + buttonEl.textContent = "Disconnect" + connected = true + } + const _onConnected = () => { statusEl.textContent = "Connected" buttonEl.textContent = "Disconnect" From f369ab4c1a6754fc0907741c982eaf8b0ad604ff Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Tue, 29 Apr 2025 11:23:33 -0300 Subject: [PATCH 2/5] Printing each new ice candidate. --- examples/p2p-webrtc/voice-agent/index.html | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/examples/p2p-webrtc/voice-agent/index.html b/examples/p2p-webrtc/voice-agent/index.html index 4ee386627..ddc873398 100644 --- a/examples/p2p-webrtc/voice-agent/index.html +++ b/examples/p2p-webrtc/voice-agent/index.html @@ -56,6 +56,7 @@ iceServers: [{ urls: ["stun:stun.l.google.com:19302"] }], }; const pc = new RTCPeerConnection(config) + addPeerConnectionEventListeners(pc) pc.ontrack = e => audioEl.srcObject = e.streams[0] // SmallWebRTCTransport expects to receive both transceivers pc.addTransceiver(audioTrack, { direction: 'sendrecv' }) @@ -77,18 +78,28 @@ _onConnecting() const audioStream = await navigator.mediaDevices.getUserMedia({audio: true}) peerConnection= await createSmallWebRTCConnection(audioStream.getAudioTracks()[0]) - peerConnection.oniceconnectionstatechange = () => { - console.log("oniceconnectionstatechange", peerConnection?.iceConnectionState) + } + + const addPeerConnectionEventListeners = (pc) => { + pc.oniceconnectionstatechange = () => { + console.log("oniceconnectionstatechange", pc?.iceConnectionState) } - peerConnection.onconnectionstatechange = () => { - console.log("onconnectionstatechange", peerConnection?.connectionState) - let connectionState = peerConnection?.connectionState + pc.onconnectionstatechange = () => { + console.log("onconnectionstatechange", pc?.connectionState) + let connectionState = pc?.connectionState if (connectionState === 'connected') { _onConnected() } else if (connectionState === 'disconnected') { _onDisconnected() } } + pc.onicecandidate = (event) => { + if (event.candidate) { + console.log("New ICE candidate:", event.candidate); + } else { + console.log("All ICE candidates have been sent."); + } + }; } const _onConnecting = () => { From c3c4952abfd9bf773b81702149d8324fb91813ea Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Tue, 29 Apr 2025 11:34:58 -0300 Subject: [PATCH 3/5] Reducing the timeout to 2 seconds for gathering the ice candidates. --- examples/p2p-webrtc/voice-agent/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/p2p-webrtc/voice-agent/index.html b/examples/p2p-webrtc/voice-agent/index.html index ddc873398..e0250a58f 100644 --- a/examples/p2p-webrtc/voice-agent/index.html +++ b/examples/p2p-webrtc/voice-agent/index.html @@ -24,7 +24,7 @@ let connected = false let peerConnection = null - const waitForIceGatheringComplete = async (pc, timeoutMs = 5000) => { + const waitForIceGatheringComplete = async (pc, timeoutMs = 2000) => { if (pc.iceGatheringState === 'complete') return; console.log("Waiting for ICE gathering to complete..."); return new Promise((resolve) => { From b347ca472faaf8b7ba8e043d39bf219966888670 Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Tue, 29 Apr 2025 12:04:20 -0300 Subject: [PATCH 4/5] Checking the state again to avoid any eventual race condition --- examples/p2p-webrtc/voice-agent/index.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/p2p-webrtc/voice-agent/index.html b/examples/p2p-webrtc/voice-agent/index.html index e0250a58f..a6def2b30 100644 --- a/examples/p2p-webrtc/voice-agent/index.html +++ b/examples/p2p-webrtc/voice-agent/index.html @@ -26,7 +26,7 @@ const waitForIceGatheringComplete = async (pc, timeoutMs = 2000) => { if (pc.iceGatheringState === 'complete') return; - console.log("Waiting for ICE gathering to complete..."); + console.log("Waiting for ICE gathering to complete. Current state:", pc.iceGatheringState); return new Promise((resolve) => { let timeoutId; const checkState = () => { @@ -47,6 +47,8 @@ }; pc.addEventListener('icegatheringstatechange', checkState); timeoutId = setTimeout(onTimeout, timeoutMs); + // Checking the state again to avoid any eventual race condition + checkState(); }); }; From d80d385b2f77e3a5cd21ca2f2fd193f4ccedc232 Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Tue, 29 Apr 2025 12:19:59 -0300 Subject: [PATCH 5/5] Adding a section explaining about ice servers. --- examples/p2p-webrtc/voice-agent/README.md | 40 ++++++++++++++++++++++ examples/p2p-webrtc/voice-agent/index.html | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/examples/p2p-webrtc/voice-agent/README.md b/examples/p2p-webrtc/voice-agent/README.md index 17bf165af..a4f6f0b2a 100644 --- a/examples/p2p-webrtc/voice-agent/README.md +++ b/examples/p2p-webrtc/voice-agent/README.md @@ -46,6 +46,46 @@ http://localhost:7860 --- +## WebRTC ICE Servers Configuration + +When implementing WebRTC in your project, **STUN** (Session Traversal Utilities for NAT) and **TURN** (Traversal Using Relays around NAT) +servers are usually needed in cases where users are behind routers or firewalls. + +In local networks (e.g., testing within the same home or office network), you usually don’t need to configure STUN or TURN servers. +In such cases, WebRTC can often directly establish peer-to-peer connections without needing to traverse NAT or firewalls. + +### What are STUN and TURN Servers? + +- **STUN Server**: Helps clients discover their public IP address and port when they're behind a NAT (Network Address Translation) device (like a router). +This allows WebRTC to attempt direct peer-to-peer communication by providing the public-facing IP and port. + +- **TURN Server**: Used as a fallback when direct peer-to-peer communication isn't possible due to strict NATs or firewalls blocking connections. +The TURN server relays media traffic between peers. + +### Why are ICE Servers Important? + +**ICE (Interactive Connectivity Establishment)** is a framework used by WebRTC to handle network traversal and NAT issues. +The `iceServers` configuration provides a list of **STUN** and **TURN** servers that WebRTC uses to find the best way to connect two peers. + +### Example Configuration for ICE Servers + +Here’s how you can configure a basic `iceServers` object in WebRTC for testing purposes, using Google's public STUN server: + +```javascript +const config = { + iceServers: [ + { + urls: ["stun:stun.l.google.com:19302"], // Google's public STUN server + } + ], +}; +``` + +> For testing purposes, you can either use public **STUN** servers (like Google's) or set up your own **TURN** server. +If you're running your own TURN server, make sure to include your server URL, username, and credential in the configuration. + +--- + ### 💡 Notes - Ensure all dependencies are installed before running the server. - Check the `.env` file for missing configurations. diff --git a/examples/p2p-webrtc/voice-agent/index.html b/examples/p2p-webrtc/voice-agent/index.html index a6def2b30..f39be3c28 100644 --- a/examples/p2p-webrtc/voice-agent/index.html +++ b/examples/p2p-webrtc/voice-agent/index.html @@ -55,7 +55,7 @@ const createSmallWebRTCConnection = async (audioTrack) => { const config = { - iceServers: [{ urls: ["stun:stun.l.google.com:19302"] }], + iceServers: [], }; const pc = new RTCPeerConnection(config) addPeerConnectionEventListeners(pc)