WebRTC (Web Real-Time Communication) is a browser API that enables real-time peer-to-peer communication including video, audio, and data sharing. This comprehensive guide covers everything you need to know.
What is WebRTC?
WebRTC is a browser API for building real-time communication applications without plugins or external software.
// Basic WebRTC - peer-to-peer video
const pc = new RTCPeerConnection(iceServers);
pc.ontrack = (event) => {
document.getElementById('video').srcObject = event.streams[0];
};
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => stream.getTracks().forEach(track => pc.addTrack(track, stream)));
Key Features
- Video/Audio - Real-time media streaming
-
- P2P - Direct peer-to-peer connections
-
- Data Channels - Arbitrary data transfer
-
- NAT Traversal - ICE, STUN, TURN
-
- Encryption - DTLS/SRTP built-in
-
- Standards-based - W3C/IETF standard
Core Components
RTCPeerConnection
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:turn.example.com:3478',
username: 'user',
credential: 'pass'
}
]
};
const pc = new RTCPeerConnection(config);
// Handle remote tracks
pc.ontrack = (event) => {
console.log('Received remote track:', event.track.kind);
const stream = event.streams[0];
// Add stream to video element
};
// Handle ICE candidates
pc.onicecandidate = (event) => {
if (event.candidate) {
// Send candidate to peer via signaling
sendSignalingMessage({ type: 'candidate', candidate: event.candidate });
}
};
// Connection state
pc.onconnectionstatechange = () => {
console.log('Connection state:', pc.connectionState);
};
// ICE connection state
pc.oniceconnectionstatechange = () => {
console.log('ICE state:', pc.iceConnectionState);
};
Media Streams
// Get user media
async function getUserMedia() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
frameRate: { ideal: 30 }
},
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});
return stream;
} catch (error) {
console.error('Error accessing media devices:', error);
}
}
// Display local video
const localVideo = document.getElementById('localVideo');
localVideo.srcObject = localStream;
localVideo.muted = true; // Mute local playback
RTCSessionDescription
// Create offer
async function createOffer(pc) {
const offer = await pc.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true
});
await pc.setLocalDescription(offer);
return offer;
}
// Create answer
async function createAnswer(pc) {
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
return answer;
}
// Set remote description
async function setRemoteDescription(pc, description) {
await pc.setRemoteDescription(new RTCSessionDescription(description));
}
Signaling
Server Setup (Node.js)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const rooms = new Map();
wss.on('connection', (ws) => {
let roomId = null;
let clientId = null;
ws.on('message', (message) => {
const data = JSON.parse(message);
switch (data.type) {
case 'join':
roomId = data.roomId;
clientId = Date.now().toString();
if (!rooms.has(roomId)) {
rooms.set(roomId, new Set());
}
rooms.get(roomId).add({ ws, clientId });
// Notify others in room
broadcastToRoom(roomId, {
type: 'user-joined',
clientId
}, clientId);
// Send existing users to new client
const existingClients = Array.from(rooms.get(roomId))
.filter(c => c.clientId !== clientId)
.map(c => c.clientId);
ws.send(JSON.stringify({
type: 'room-users',
users: existingClients
}));
break;
case 'offer':
case 'answer':
case 'candidate':
// Forward to specific client
sendToClient(roomId, data.targetClientId, data);
break;
case 'leave':
removeFromRoom(roomId, clientId);
break;
}
});
ws.on('close', () => {
if (roomId && clientId) {
removeFromRoom(roomId, clientId);
}
});
});
function broadcastToRoom(roomId, message, excludeClientId) {
if (!rooms.has(roomId)) return;
const data = JSON.stringify(message);
rooms.get(roomId).forEach(client => {
if (client.clientId !== excludeClientId && client.ws.readyState === WebSocket.OPEN) {
client.ws.send(data);
}
});
}
function sendToClient(roomId, targetClientId, message) {
if (!rooms.has(roomId)) return;
const data = JSON.stringify(message);
rooms.get(roomId).forEach(client => {
if (client.clientId === targetClientId && client.ws.readyState === WebSocket.OPEN) {
client.ws.send(data);
}
});
}
function removeFromRoom(roomId, clientId) {
if (!rooms.has(roomId)) return;
rooms.get(roomId).forEach(client => {
if (client.clientId === clientId) {
client.ws.send(JSON.stringify({ type: 'user-left', clientId }));
rooms.get(roomId).delete(client);
}
});
}
Client Implementation
class SignalingClient {
constructor(url, roomId) {
this.ws = new WebSocket(url);
this.roomId = roomId;
this.pc = null;
this.clientId = null;
this.setupWebSocket();
}
setupWebSocket() {
this.ws.onopen = () => {
this.send({ type: 'join', roomId: this.roomId });
};
this.ws.onmessage = async (event) => {
const data = JSON.parse(event.data);
await this.handleSignalingMessage(data);
};
}
async handleSignalingMessage(data) {
switch (data.type) {
case 'room-users':
// Handle existing users
data.users.forEach(userId => this.createPeerConnection(userId, true));
break;
case 'user-joined':
this.createPeerConnection(data.clientId, false);
break;
case 'offer':
await this.handleOffer(data);
break;
case 'answer':
await this.handleAnswer(data);
break;
case 'candidate':
await this.handleCandidate(data);
break;
case 'user-left':
this.removePeerConnection(data.clientId);
break;
}
}
async createPeerConnection(peerId, isInitiator) {
this.pc = new RTCPeerConnection(iceServers);
this.pc.ontrack = (event) => {
// Display remote stream
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = event.streams[0];
};
// Add local tracks
const localStream = await getUserMedia();
localStream.getTracks().forEach(track => {
this.pc.addTrack(track, localStream);
});
if (isInitiator) {
const offer = await this.pc.createOffer();
await this.pc.setLocalDescription(offer);
this.send({ type: 'offer', offer, targetClientId: peerId });
}
}
async handleOffer(data) {
await this.pc.setRemoteDescription(data.offer);
const answer = await this.pc.createAnswer();
await this.pc.setLocalDescription(answer);
this.send({ type: 'answer', answer, targetClientId: data.clientId });
}
async handleAnswer(data) {
await this.pc.setRemoteDescription(data.answer);
}
async handleCandidate(data) {
await this.pc.addIceCandidate(new RTCIceCandidate(data.candidate));
}
send(message) {
this.ws.send(JSON.stringify(message));
}
}
Data Channels
Sending Data
const pc = new RTCPeerConnection(config);
// Create data channel
const dataChannel = pc.createDataChannel('chat', {
ordered: true, // Guarantee order
maxRetransmits: 30
});
dataChannel.onopen = () => {
console.log('Data channel opened');
};
dataChannel.onmessage = (event) => {
console.log('Received:', event.data);
};
// Send message
dataChannel.send(JSON.stringify({
type: 'message',
text: 'Hello!',
timestamp: Date.now()
}));
Receiving Data
pc.ondatachannel = (event) => {
const receiveChannel = event.channel;
receiveChannel.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
receiveChannel.onopen = () => {
console.log('Receive channel opened');
};
};
Advanced Features
Screen Sharing
async function shareScreen() {
try {
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: 'always'
},
audio: false
});
// Replace video track
const videoTrack = screenStream.getVideoTracks()[0];
const sender = pc.getSenders().find(s => s.track?.kind === 'video');
sender.replaceTrack(videoTrack);
// Handle stop sharing
videoTrack.onended = () => {
// Switch back to camera
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
const cameraTrack = stream.getVideoTracks()[0];
sender.replaceTrack(cameraTrack);
});
};
} catch (error) {
console.error('Error sharing screen:', error);
}
}
Connection Statistics
async function getStats(pc) {
const stats = await pc.getStats();
stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.kind === 'video') {
console.log('Video bytes received:', report.bytesReceived);
console.log('Packets lost:', report.packetsLost);
console.log('Jitter:', report.jitter);
}
if (report.type === 'outbound-rtp' && report.kind === 'video') {
console.log('Video bytes sent:', report.bytesSent);
console.log('Round trip time:', report.roundTripTime);
}
});
}
// Monitor every 2 seconds
setInterval(() => getStats(pc), 2000);
Audio/Video Controls
// Mute/unmute
function toggleAudio(enabled) {
const audioTrack = localStream.getAudioTracks()[0];
audioTrack.enabled = enabled;
}
function toggleVideo(enabled) {
const videoTrack = localStream.getVideoTracks()[0];
videoTrack.enabled = enabled;
}
// Adjust bitrate
async function setVideoBitrate(pc, bitrate) {
const sender = pc.getSenders().find(s => s.track?.kind === 'video');
const params = sender.getParameters();
if (!params.encodings) {
params.encodings = [{}];
}
params.encodings[0].maxBitrate = bitrate;
await sender.setParameters(params);
}
Error Handling
pc.oniceconnectionstatechange = () => {
switch (pc.iceConnectionState) {
case 'failed':
console.error('ICE connection failed');
// Try restarting ICE
pc.restartIce();
break;
case 'disconnected':
console.log('ICE disconnected');
// Attempt to reconnect
break;
case 'closed':
console.log('ICE closed');
break;
}
};
pc.oniceerror = (event) => {
console.error('ICE error:', event);
};
pc.ontrackerror = (event) => {
console.error('Track error:', event);
};
Browser Support
WebRTC is supported in all modern browsers:
- Chrome 56+
- Firefox 22+
- Safari 11+
- Edge 12+
- Opera 43+
External Resources
Conclusion
WebRTC enables powerful real-time communication applications directly in the browser. Key points:
- Use RTCPeerConnection for media and data channels
- Implement signaling server for connection setup
- Handle ICE candidates for NAT traversal
- Use STUN/TURN servers for production
- Monitor connection stats for quality
WebRTC powers video chat apps, file sharing, gaming, and more - all without plugins or external software.
Comments