What Is WebRTC and How Does It Work in JavaScript?

Web Real-Time Communication (WebRTC) is an open-source standard and framework that enables web browsers and mobile applications to exchange real-time video, audio, and arbitrary data directly between devices without requiring an intermediate media server. This article explores the core architecture of WebRTC, the role of signaling servers and NAT traversal (STUN/TURN), and the step-by-step JavaScript workflow required to initialize and maintain direct peer-to-peer (P2P) connections using the RTCPeerConnection API.


Core Components of WebRTC

WebRTC relies on three primary JavaScript APIs:

  1. MediaDevices.getUserMedia(): Captures audio and video tracks from hardware devices (microphones and cameras).
  2. RTCPeerConnection: Manages the complete lifecycle of a peer-to-peer connection, including bandwidth management, encryption, and audio/video streaming.
  3. RTCDataChannel: Establishes a bidirectional, low-latency data channel for transferring arbitrary data directly between peers.

The Role of Signaling and NAT Traversal

While WebRTC transfers media directly between browsers, peers cannot discover each other automatically. Most user devices sit behind routers, firewalls, and Network Address Translation (NAT) layers, meaning they do not possess a static, public IP address.

To resolve this, WebRTC requires two auxiliary mechanisms:


Step-by-Step: Establishing a P2P Connection in JavaScript

Connecting two peers (Peer A and Peer B) follows a negotiation process called the Offer/Answer model, governed by the Session Description Protocol (SDP) and Interactive Connectivity Establishment (ICE).

Step 1: Initialize the Peer Connection

Both peers create an instance of RTCPeerConnection, passing configuration options that include STUN and TURN server URLs.

const configuration = {
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' }
  ]
};

const peerConnection = new RTCPeerConnection(configuration);

Step 2: Capture and Attach Local Media

The initiating peer accesses local media and attaches the tracks to the connection.

const localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });

localStream.getTracks().forEach(track => {
  peerConnection.addTrack(track, localStream);
});

Step 3: Create and Exchange the SDP Offer

Peer A generates an SDP offer describing its media capabilities and network configuration, sets it as its local description, and transmits it to Peer B via the signaling server.

// Peer A
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);

signalingServer.send(JSON.stringify({ type: 'offer', sdp: offer }));

Step 4: Handle the Offer and Return an SDP Answer

Peer B receives the offer, sets it as its remote description, generates an SDP answer, sets it as its local description, and sends it back to Peer A.

// Peer B
signalingServer.onmessage = async (message) => {
  const data = JSON.parse(message.data);

  if (data.type === 'offer') {
    await peerConnection.setRemoteDescription(new RTCSessionDescription(data.sdp));
    
    const answer = await peerConnection.createAnswer();
    await peerConnection.setLocalDescription(answer);
    
    signalingServer.send(JSON.stringify({ type: 'answer', sdp: answer }));
  }
};

Upon receiving the answer, Peer A updates its configuration:

// Peer A
await peerConnection.setRemoteDescription(new RTCSessionDescription(data.sdp));

Step 5: Gather and Exchange ICE Candidates

As soon as local descriptions are set, the browser discovers its network endpoints (ICE candidates). Each candidate must be sent to the remote peer via signaling.

// Listen for local ICE candidates and send them to the remote peer
peerConnection.onicecandidate = (event) => {
  if (event.candidate) {
    signalingServer.send(JSON.stringify({ type: 'candidate', candidate: event.candidate }));
  }
};

// Add received ICE candidates from the remote peer
signalingServer.onmessage = async (message) => {
  const data = JSON.parse(message.data);
  if (data.type === 'candidate') {
    await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
  }
};

Step 6: Receive Remote Media Streams

Once the ICE candidates match and the connection is verified, the track event fires on both peers, providing access to the remote audio and video streams.

peerConnection.ontrack = (event) => {
  const [remoteStream] = event.streams;
  const remoteVideoElement = document.getElementById('remoteVideo');
  remoteVideoElement.srcObject = remoteStream;
};

Conclusion

WebRTC enables encrypted, low-latency, peer-to-peer browser communication. JavaScript handles device media capture, coordinates the Offer/Answer SDP exchange, and handles ICE candidate routing. Once this initial signaling phase completes, data and media flow directly between peers without traversing application servers.