Merge pull request #1581 from pipecat-ai/voice_agent_ice_servers

Configuring the voice-agent example to wait for all the ice candidates.
This commit is contained in:
Filipi da Silva Fuchter
2025-04-29 13:30:12 -03:00
committed by GitHub
2 changed files with 88 additions and 7 deletions

View File

@@ -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 dont 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
Heres 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.

View File

@@ -24,27 +24,47 @@
let connected = false
let peerConnection = null
/*const waitForIceGatheringComplete = async (pc) => {
const waitForIceGatheringComplete = async (pc, timeoutMs = 2000) => {
if (pc.iceGatheringState === 'complete') return;
console.log("Waiting for ICE gathering to complete. Current state:", pc.iceGatheringState);
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);
// Checking the state again to avoid any eventual race condition
checkState();
});
}*/
};
const createSmallWebRTCConnection = async (audioTrack) => {
const pc = new RTCPeerConnection()
const config = {
iceServers: [],
};
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' })
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,16 +77,37 @@
}
const connect = async () => {
_onConnecting()
const audioStream = await navigator.mediaDevices.getUserMedia({audio: true})
peerConnection= await createSmallWebRTCConnection(audioStream.getAudioTracks()[0])
peerConnection.onconnectionstatechange = () => {
let connectionState = peerConnection?.connectionState
}
const addPeerConnectionEventListeners = (pc) => {
pc.oniceconnectionstatechange = () => {
console.log("oniceconnectionstatechange", pc?.iceConnectionState)
}
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 = () => {
statusEl.textContent = "Connecting"
buttonEl.textContent = "Disconnect"
connected = true
}
const _onConnected = () => {