Prompt
How do I add video calling to my app with WebRTC?
Latest observation
Nov 2, 2026
OpenAI APIWeb search: off
To add video calling with WebRTC, you generally need 4 parts:
- Get camera/microphone access
- Connect two peers using signaling
- Establish the WebRTC peer connection
- Handle NAT traversal with STUN/TURN
1) Basic architecture
WebRTC does not handle discovery of the other peer by itself. You need a signaling server to exchange:
- SDP offers/answers
- ICE candidates
Common signaling options:
- WebSocket
- Socket.IO
- Firebase / Supabase realtime
- Any custom HTTP + WebSocket backend
2) Client-side flow
A. Get local media
Use getUserMedia to access camera and microphone:
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
localVideo.srcObject = localStream;
B. Create RTCPeerConnection
Configure STUN/TURN servers:
const pc = new RTCPeerConnection({
iceServers: [
{ urls: "stun:stun.l.google.com:19302" },
// For production, add TURN server too
// { urls: "turn:turn.example.com:3478", username: "user", credential: "pass" }
]
});
C. Add local tracks
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
D. Send ICE candidates through signaling
pc.onicecandidate = (event) => {
if (event.candidate) {
signalingSocket.send(JSON.stringify({
type: "ice-candidate",
candidate: event.candidate
}));
}
};
E. Receive remote stream
pc.ontrack = (event) => {
remoteVideo.srcObject = event.streams[0];
};
3) Offer/answer exchange
Caller
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signalingSocket.send(JSON.stringify({
type: "offer",
offer
}));
Callee
signalingSocket.onmessage = async (message) => {
const data = JSON.parse(message.data);
if (data.type === "offer") {
await pc.setRemoteDescription(new RTCSessionDescription(data.offer));
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
signalingSocket.send(JSON.stringify({
type: "answer",
answer
}));
}
};
Caller receives answer
if (data.type === "answer") {
await pc.setRemoteDescription(new RTCSessionDescription(data.answer));
}
Handle ICE candidates
if (data.type === "ice-candidate") {
await pc.addIceCandidate(new RTCIceCandidate(data.candidate));
}
4) Minimal example structure
HTML
<video id="localVideo" autoplay playsinline muted></video>
<video id="remoteVideo" autoplay playsinline></video>
<button id="startCall">Start Call</button>
JS
const localVideo = document.getElementById("localVideo");
const remoteVideo = document.getElementById("remoteVideo");
const startCall = document.getElementById("startCall");
let pc;
let localStream;
const signalingSocket = new WebSocket("wss://your-signaling-server.example");
async function init() {
localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
localVideo.srcObject = localStream;
pc = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
});
localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
pc.ontrack = (event) => {
remoteVideo.srcObject = event.streams[0];
};
pc.onicecandidate = (event) => {
if (event.candidate) {
signalingSocket.send(JSON.stringify({
type: "ice-candidate",
candidate: event.candidate
}));
}
};
signalingSocket.onmessage = async (message) => {
const data = JSON.parse(message.data);
if (data.type === "offer") {
await pc.setRemoteDescription(data.offer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
signalingSocket.send(JSON.stringify({ type: "answer", answer }));
}
if (data.type === "answer") {
await pc.setRemoteDescription(data.answer);
}
if (data.type === "ice-candidate") {
await pc.addIceCandidate(data.candidate);
}
};
}
startCall.onclick = async () => {
await init();
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signalingSocket.send(JSON.stringify({ type: "offer", offer }));
};
5) STUN vs TURN
- STUN helps peers discover their public address.
- TURN relays media when direct peer-to-peer fails.
For real-world apps, you almost always need a TURN server because some networks block direct P2P connectivity.
Popular TURN options:
- coturn (self-hosted)
- Twilio Network Traversal
- Xirsys
- Cloudflare Calls / other managed services
6) Production considerations
- Use HTTPS/WSS only
- Handle reconnects and call drops
- Manage device permission errors
- Add mute/unmute, camera toggle, hang up
- Support mobile browsers carefully
- Use TURN for reliability
- Consider SFU if you need group calls:
- Janus
- mediasoup
- Jitsi
- LiveKit
- Twilio Video
7) If you want group calls
For more than 2 people, a pure mesh WebRTC setup does not scale well. Use an SFU (Selective Forwarding Unit) instead of peer-to-peer mesh.
If you want, I can give you:
- a complete working two-user WebRTC demo with Node.js + Socket.IO, or
- a React example for integrating WebRTC into your app.