Intermediate
20 min
Full Guide
WebRTC Fundamentals
Understand WebRTC architecture, peer-to-peer connections, and browser APIs
What is WebRTC?
WebRTC (Web Real-Time Communication) is a free, open-source technology that enables peer-to-peer communication directly between browsers. Unlike WebSocket where all data flows through a server, WebRTC allows browsers to communicate directly with each other.
WebRTC is used by applications like Google Meet, Discord, Zoom (web version), and many video chat, screen sharing, and file transfer applications. It supports audio, video, and arbitrary data transfer.
🎯 Why Peer-to-Peer?
✓ Advantages
- • Lower latency - No server hop
- • Reduced server cost - No bandwidth for media
- • Privacy - Data doesn't pass through servers
- • Scalability - Server doesn't bottleneck
✗ Challenges
- • NAT traversal - Peers behind routers
- • Signaling needed - Peers must find each other
- • Complex API - More setup than WebSocket
- • Firewall issues - Some networks block P2P
WebRTC Architecture Overview
// WebRTC communication flow
/*
Browser A Signaling Server Browser B
| | |
| 1. Create Offer (SDP) | |
|----------------------------->| |
| | 2. Forward Offer |
| |------------------------------->|
| | |
| | 3. Create Answer (SDP) |
| |<-------------------------------|
| 4. Receive Answer | |
|<-----------------------------| |
| | |
| 5. Exchange ICE Candidates | 5. Exchange ICE Candidates |
|<---------------------------->|<------------------------------>|
| | |
| |
|<============== 6. Direct P2P Connection =====================>|
| (Media/Data flows directly) |
*/
// Key components:
// 1. Signaling Server - Helps peers find each other (WebSocket, HTTP, etc.)
// 2. SDP (Session Description Protocol) - Describes media capabilities
// 3. ICE (Interactive Connectivity Establishment) - Finds best path
// 4. STUN Server - Discovers public IP/port
// 5. TURN Server - Relay when P2P fails
Understanding NAT and ICE
// The NAT Problem
/*
Most devices are behind NAT (Network Address Translation):
Private IP: 192.168.1.100:5000 → Router NAT → Public IP: 203.0.113.1:54321
Peers don't know each other's public IP/port!
Solution: ICE framework
*/
// ICE (Interactive Connectivity Establishment) finds the best path:
// 1. Host candidates - Direct LAN connection (same network)
{ type: 'host', ip: '192.168.1.100', port: 5000 }
// 2. Server-reflexive candidates - Via STUN (public IP discovery)
{ type: 'srflx', ip: '203.0.113.1', port: 54321 }
// 3. Relay candidates - Via TURN (when P2P fails)
{ type: 'relay', ip: '10.0.0.1', port: 3478 }
// STUN (Session Traversal Utilities for NAT)
// - Lightweight server that tells you your public IP/port
// - Free services available (Google, Mozilla)
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' }
]
};
// TURN (Traversal Using Relays around NAT)
// - Fallback when direct P2P fails (strict firewalls, symmetric NAT)
// - Relays all traffic through server (adds latency, uses bandwidth)
// - Usually requires authentication (costs money for bandwidth)
const configWithTurn = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:turn.example.com:3478',
username: 'user',
credential: 'password'
}
]
};
The RTCPeerConnection API
// RTCPeerConnection is the core WebRTC API
// Create peer connection with ICE servers
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }
]
});
// Connection state events
pc.onconnectionstatechange = () => {
console.log('Connection state:', pc.connectionState);
// 'new' | 'connecting' | 'connected' | 'disconnected' | 'failed' | 'closed'
};
pc.oniceconnectionstatechange = () => {
console.log('ICE state:', pc.iceConnectionState);
// 'new' | 'checking' | 'connected' | 'completed' | 'failed' | 'disconnected' | 'closed'
};
pc.onicegatheringstatechange = () => {
console.log('ICE gathering:', pc.iceGatheringState);
// 'new' | 'gathering' | 'complete'
};
// ICE candidates
pc.onicecandidate = (event) => {
if (event.candidate) {
// Send this candidate to the remote peer via signaling
signalingChannel.send({
type: 'ice-candidate',
candidate: event.candidate
});
}
};
// Receiving remote stream
pc.ontrack = (event) => {
console.log('Received remote track:', event.track.kind);
// Attach to video element
remoteVideo.srcObject = event.streams[0];
};
// Statistics
const stats = await pc.getStats();
stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.kind === 'video') {
console.log('Frames received:', report.framesReceived);
console.log('Packets lost:', report.packetsLost);
}
});
// Close connection
pc.close();
SDP (Session Description Protocol)
// SDP describes what media formats/codecs each peer supports
// Creating an offer (initiating peer)
const offer = await pc.createOffer();
console.log(offer.sdp);
/*
v=0
o=- 4611731400430051336 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0
a=msid-semantic: WMS stream
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98
c=IN IP4 0.0.0.0
a=rtpmap:96 VP8/90000
a=rtpmap:97 VP9/90000
a=rtpmap:98 H264/90000
a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
...
*/
// Set local description
await pc.setLocalDescription(offer);
// Send offer to remote peer via signaling...
signalingChannel.send({ type: 'offer', sdp: offer.sdp });
// Remote peer receives offer and creates answer
await remotePc.setRemoteDescription(offer);
const answer = await remotePc.createAnswer();
await remotePc.setLocalDescription(answer);
// Send answer back...
signalingChannel.send({ type: 'answer', sdp: answer.sdp });
// Original peer receives answer
await pc.setRemoteDescription(answer);
// Connection is now ready for media!
Basic WebRTC Connection Flow
// Complete example: Connecting two peers
// ========== PEER A (Caller) ==========
const pcA = new RTCPeerConnection(config);
// Add local stream
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
stream.getTracks().forEach(track => pcA.addTrack(track, stream));
// Handle ICE candidates
pcA.onicecandidate = (event) => {
if (event.candidate) {
sendToSignalingServer({ type: 'ice-candidate', candidate: event.candidate, to: 'peerB' });
}
};
// Create and send offer
const offer = await pcA.createOffer();
await pcA.setLocalDescription(offer);
sendToSignalingServer({ type: 'offer', sdp: offer.sdp, to: 'peerB' });
// Receive answer
signalingServer.on('answer', async (answer) => {
await pcA.setRemoteDescription(new RTCSessionDescription(answer));
});
// Receive remote ICE candidates
signalingServer.on('ice-candidate', async (candidate) => {
await pcA.addIceCandidate(new RTCIceCandidate(candidate));
});
// ========== PEER B (Callee) ==========
const pcB = new RTCPeerConnection(config);
// Add local stream
const streamB = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
streamB.getTracks().forEach(track => pcB.addTrack(track, streamB));
// Handle incoming tracks
pcB.ontrack = (event) => {
remoteVideo.srcObject = event.streams[0];
};
// Handle ICE candidates
pcB.onicecandidate = (event) => {
if (event.candidate) {
sendToSignalingServer({ type: 'ice-candidate', candidate: event.candidate, to: 'peerA' });
}
};
// Receive offer
signalingServer.on('offer', async (offer) => {
await pcB.setRemoteDescription(new RTCSessionDescription(offer));
// Create and send answer
const answer = await pcB.createAnswer();
await pcB.setLocalDescription(answer);
sendToSignalingServer({ type: 'answer', sdp: answer.sdp, to: 'peerA' });
});
// Receive remote ICE candidates
signalingServer.on('ice-candidate', async (candidate) => {
await pcB.addIceCandidate(new RTCIceCandidate(candidate));
});
WebRTC Capabilities
// What can WebRTC transmit?
// 1. AUDIO
const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
audioStream.getAudioTracks().forEach(track => pc.addTrack(track, audioStream));
// 2. VIDEO
const videoStream = await navigator.mediaDevices.getUserMedia({
video: {
width: 1280,
height: 720,
facingMode: 'user' // 'environment' for back camera
}
});
// 3. SCREEN SHARING
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: { cursor: 'always' },
audio: true // System audio (if supported)
});
// 4. ARBITRARY DATA (RTCDataChannel)
const dataChannel = pc.createDataChannel('messages');
dataChannel.onopen = () => {
dataChannel.send('Hello!');
dataChannel.send(new Blob(['binary data']));
dataChannel.send(new ArrayBuffer(256));
};
// Codecs supported (typical):
// Audio: Opus, G.711, G.722
// Video: VP8, VP9, H.264, AV1
// Check supported codecs
const codecs = RTCRtpSender.getCapabilities('video').codecs;
console.log(codecs.map(c => c.mimeType));
// ['video/VP8', 'video/VP9', 'video/H264', ...]
Connection States Explained
// Understanding WebRTC connection states
pc.onconnectionstatechange = () => {
switch (pc.connectionState) {
case 'new':
// Just created, no networking yet
console.log('PeerConnection created');
break;
case 'connecting':
// ICE/DTLS in progress
showStatus('Connecting...');
break;
case 'connected':
// Fully connected, media flowing
showStatus('Connected!');
hideConnectingSpinner();
break;
case 'disconnected':
// Temporarily lost connection (network issue)
// May recover automatically
showStatus('Connection unstable...');
break;
case 'failed':
// Connection attempt failed (ICE failed, firewall, etc.)
showStatus('Connection failed');
offerRetryOrFallback();
break;
case 'closed':
// Connection was closed intentionally
cleanup();
break;
}
};
// ICE-specific states give more detail
pc.oniceconnectionstatechange = () => {
if (pc.iceConnectionState === 'failed') {
// ICE failed - try restarting
pc.restartIce();
}
};
// Restart ICE without full renegotiation
async function restartIce() {
const offer = await pc.createOffer({ iceRestart: true });
await pc.setLocalDescription(offer);
// Send new offer to peer...
}
⚠️ WebRTC Gotchas
- ✗ HTTPS required - getUserMedia only works on HTTPS (or localhost)
- ✗ Signaling not included - You must build/use a signaling server
- ✗ TURN costs money - Free STUN works, but TURN relay needs bandwidth
- ✗ Firewall issues - Some corporate networks block P2P entirely
- ✗ Browser differences - Test across Chrome, Firefox, Safari
- ✗ Mobile complexity - Handle app switching, camera permissions
💡 Key Concepts Summary
- ✓ RTCPeerConnection - The main API for WebRTC connections
- ✓ Signaling - Exchange SDP offers/answers and ICE candidates
- ✓ SDP - Describes media capabilities and formats
- ✓ ICE - Finds the best path to connect peers
- ✓ STUN - Discovers your public IP address
- ✓ TURN - Fallback relay when P2P fails