Advanced Backend·Advanced·10 questions

WebRTC

Peer-to-peer video, audio, and data — ICE, STUN, TURN, and signaling.

Overview

WebRTC (Web Real-Time Communication) is a browser API that enables direct peer-to-peer audio, video, and data transfer between browsers — without a media server. Technologies like Google Meet, Zoom web, and Discord's browser client are built on WebRTC. The key advantage: once the connection is established, media flows directly peer-to-peer, which means low latency and no server bandwidth costs for the actual media traffic.

Setting up a WebRTC call requires a signaling server (typically built with WebSockets). The two peers exchange SDP (Session Description Protocol) messages: the caller creates an SDP 'offer' describing its media capabilities, and the callee creates an 'answer'. They also exchange ICE (Interactive Connectivity Establishment) candidates — possible network paths for the connection. The browser API navigator.mediaDevices.getUserMedia() requests access to the user's camera and microphone, returning a MediaStream.

STUN servers help peers discover their public IP address when behind NAT (Network Address Translation). TURN servers relay media through an intermediate server when direct P2P fails — needed for users behind strict corporate firewalls or symmetric NATs. TURN servers are expensive (bandwidth costs) so they're only used as a fallback. RTCDataChannel lets you send arbitrary data (text, files, game state) peer-to-peer with low latency. For group calls with many participants, an SFU (Selective Forwarding Unit) like mediasoup or livekit is used instead of full mesh P2P.

Code Example

WebRTC Video Call: getUserMedia, RTCPeerConnection, Signaling
javascript
// ── Client-side WebRTC Video Call ────────────────
const socket = io('wss://signal.example.com')

const iceConfig = {
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },  // Free STUN
    {
      urls: 'turn:turn.example.com:3478',       // Paid TURN (fallback)
      username: 'user',
      credential: 'password'
    }
  ]
}

let pc = null

async function startCall(remoteUserId) {
  // 1. Get local camera + mic
  const localStream = await navigator.mediaDevices.getUserMedia({
    video: { width: 1280, height: 720 },
    audio: true
  })
  document.getElementById('localVideo').srcObject = localStream

  // 2. Create peer connection
  pc = new RTCPeerConnection(iceConfig)

  // 3. Add local tracks to connection
  localStream.getTracks().forEach(track => pc.addTrack(track, localStream))

  // 4. When remote stream arrives, show it
  pc.ontrack = ({ streams }) => {
    document.getElementById('remoteVideo').srcObject = streams[0]
  }

  // 5. Send ICE candidates to remote peer via signaling
  pc.onicecandidate = ({ candidate }) => {
    if (candidate) socket.emit('ice-candidate', { candidate, to: remoteUserId })
  }

  // 6. Create and send SDP offer
  const offer = await pc.createOffer()
  await pc.setLocalDescription(offer)
  socket.emit('offer', { sdp: offer, to: remoteUserId })
}

// ── Callee: handle incoming offer ────────────────
socket.on('offer', async ({ sdp, from }) => {
  pc = new RTCPeerConnection(iceConfig)

  const localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
  localStream.getTracks().forEach(track => pc.addTrack(track, localStream))
  pc.ontrack = ({ streams }) => { document.getElementById('remoteVideo').srcObject = streams[0] }

  await pc.setRemoteDescription(new RTCSessionDescription(sdp))
  const answer = await pc.createAnswer()
  await pc.setLocalDescription(answer)
  socket.emit('answer', { sdp: answer, to: from })
})

// Handle answer + ICE candidates
socket.on('answer', ({ sdp }) => pc.setRemoteDescription(new RTCSessionDescription(sdp)))
socket.on('ice-candidate', ({ candidate }) => pc.addIceCandidate(new RTCIceCandidate(candidate)))

Test Your Knowledge

Ready to test what you've learned? Take the quiz with 10 questions and see your score.