WebRTC
WebRTC (Web Real-Time Communication) enables real-time communication directly between web browsers without plugins or server routing.
WebRTC: Real-Time Communication in the Browser
WebRTC (Web Real-Time Communication) is an open standard and set of browser APIs that let two browsers exchange audio, video, and arbitrary data directly with each other — peer-to-peer — without routing the media through a server and without any plugin. It powers video conferencing, voice calls, screen sharing, live game state, and low-latency file transfer.
This chapter explains what WebRTC gives you, the three APIs it is built from, the signaling and NAT-traversal concepts that confuse most beginners, and a complete working data-channel example you can run in this page.
What WebRTC actually does
The browser already knows how to talk to a server (see Fetch API and WebSockets). WebRTC adds the missing piece: a way for two clients to talk to each other. Once a peer connection is open, media and data travel along the shortest possible path — often directly between the two machines — which is why WebRTC latency is so much lower than relaying everything through your backend.
Typical uses:
- Audio and video calling — real-time voice/video, like Zoom or Google Meet.
- Screen sharing — capture a screen or window with
getDisplayMedia()and stream it to a peer. - Real-time data — game state, chat, cursor positions, or file chunks over a
RTCDataChannel.
Why use WebRTC
- Peer-to-peer, low latency. Media flows directly between peers, not through your server, so round-trips are short and your bandwidth bill stays small.
- Plugin-free. Built into every modern browser — no Flash, no native app, no download.
- Encrypted by default. Media uses SRTP and data channels use DTLS; encryption is mandatory and cannot be turned off.
- Cross-platform. Works on desktop and mobile browsers and from native apps via the same standard.
- Open standard. Maintained by the W3C and IETF, so it keeps improving without vendor lock-in.
The three core APIs
WebRTC is really three APIs working together:
- MediaStream API (
getUserMedia) — grabs audio/video from the microphone, camera, or screen as aMediaStream. - RTCPeerConnection — the heart of WebRTC. It negotiates, encrypts, and maintains the connection between two peers and carries the media tracks.
- RTCDataChannel — a bidirectional channel for sending arbitrary data (strings or binary) peer-to-peer, similar to WebSockets but without a server in the middle.
Signaling: the part WebRTC does not do
Two browsers cannot find each other out of thin air. Before a peer connection can open, the peers must exchange two kinds of information:
- SDP (Session Description Protocol) — an "offer" and "answer" describing codecs, media formats, and connection parameters.
- ICE candidates — possible network addresses (IP/port pairs) at which each peer can be reached.
WebRTC does not define how this hand-off happens — that is your job, and it is called signaling. You build a signaling channel with whatever server transport you like; WebSockets is the most common choice. The flow is always:
- Caller creates an offer and sends it through the signaling server.
- Callee receives the offer, creates an answer, and sends it back.
- Both peers trade ICE candidates as they are discovered.
- The direct peer-to-peer connection is established; the signaling server is no longer needed.
STUN and TURN: getting through firewalls
Most devices sit behind NAT (Network Address Translation) and firewalls, so a peer rarely knows its own public address. Two server types solve this:
- STUN server — tells a peer its public IP/port so peers can attempt a direct connection. Cheap and stateless; Google runs free public ones.
- TURN server — relays the media when a direct connection is impossible (strict corporate firewalls). It costs bandwidth, so it is a fallback, not the default.
You list these servers in the iceServers config passed to RTCPeerConnection:
const configuration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
// A TURN server is added for hard-to-reach networks:
// { urls: 'turn:turn.example.com', username: 'user', credential: 'pass' }
],
};
const peerConnection = new RTCPeerConnection(configuration);Example: capturing the camera with getUserMedia
The first step of any video call is getting the local stream. getUserMedia returns a Promise, so you can use async/await:
<video id="localVideo" autoplay playsinline muted></video>const localVideo = document.getElementById('localVideo');
async function startCamera() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
localVideo.srcObject = stream; // show the live camera feed
return stream;
} catch (error) {
console.error('Could not access camera/microphone:', error.message);
}
}
startCamera();getUserMedia only works on a secure context (HTTPS or localhost) and prompts the user for permission — you can never silently record someone.
Example: a complete data channel between two peers
The clearest way to see WebRTC work is to connect two RTCPeerConnection objects to each other in a single script. Here both peers live in the same page, so we can wire their signaling directly instead of through a server — the SDP/ICE exchange is exactly what a real signaling server would carry. Paste this into a browser console on any HTTPS page to watch it run:
// Two peers, normally on different machines.
const caller = new RTCPeerConnection();
const callee = new RTCPeerConnection();
// --- Signaling: hand each peer's ICE candidates to the other.
// In production this travels over WebSockets; here we call directly.
caller.onicecandidate = (e) => e.candidate && callee.addIceCandidate(e.candidate);
callee.onicecandidate = (e) => e.candidate && caller.addIceCandidate(e.candidate);
// The callee listens for the channel the caller opens.
callee.ondatachannel = (event) => {
const channel = event.channel;
channel.onmessage = (e) => {
console.log('Callee received:', e.data);
channel.send('pong'); // reply over the same channel
};
};
// The caller creates the data channel.
const channel = caller.createDataChannel('chat');
channel.onopen = () => channel.send('ping');
channel.onmessage = (e) => console.log('Caller received:', e.data);
// --- Offer / answer negotiation.
async function connect() {
const offer = await caller.createOffer();
await caller.setLocalDescription(offer);
await callee.setRemoteDescription(offer);
const answer = await callee.createAnswer();
await callee.setLocalDescription(answer);
await caller.setRemoteDescription(answer);
}
connect();
// Logs:
// Callee received: ping
// Caller received: pongThe pattern is the same for video: instead of createDataChannel, you call peerConnection.addTrack(track, stream) for each track from getUserMedia, and read incoming media in an ontrack handler:
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
stream.getTracks().forEach((track) => peerConnection.addTrack(track, stream));
// The remote peer's media arrives here:
peerConnection.ontrack = (event) => {
document.getElementById('remoteVideo').srcObject = event.streams[0];
};Common gotchas
- It needs HTTPS.
getUserMediaandgetDisplayMediafail outside a secure context (HTTPS orlocalhost). - You still need a signaling server. WebRTC handles the media, but you must build the offer/answer/ICE exchange yourself — usually over WebSockets.
- Always create the offer/answer in order.
setLocalDescriptionmust run beforesetRemoteDescriptionfor the matching side, or negotiation fails. - Don't forget a TURN server in production. STUN alone fails on restrictive networks; without TURN, ~10–20% of real-world connections will not establish.
- Permissions can be denied. Wrap
getUserMediain try/catch and handle the rejection gracefully.
Summary
WebRTC gives the browser true peer-to-peer audio, video, and data. Use getUserMedia to capture media, RTCPeerConnection to negotiate and carry the connection, and RTCDataChannel for arbitrary data. WebRTC does not provide signaling — you exchange SDP offers/answers and ICE candidates yourself, typically over WebSockets, and configure STUN/TURN servers to traverse NAT. With those pieces in place you can build video calling, screen sharing, and real-time collaboration entirely in the browser.