Frontend Development 15 min read

Building a 1‑to‑1 WebRTC Real‑Time Audio/Video Call in the Browser

This article explains how to create a browser‑based 1‑to‑1 real‑time audio/video communication application using WebRTC APIs, covering media capture, SDP and ICE handling, signaling with socket.io, peer‑to‑peer connection setup, data channels, and NAT traversal techniques.

ByteFE
ByteFE
ByteFE
Building a 1‑to‑1 WebRTC Real‑Time Audio/Video Call in the Browser

WebRTC provides a standard API that enables real‑time audio and video communication directly in web browsers; most browsers and operating systems support it, allowing developers to build 1‑to‑1 calls without external plugins.

Audio/Video Capture

The getUserMedia API obtains a MediaStream from the user's camera and microphone, which can be assigned to a video element's srcObject for local playback.

API:navigator.mediaDevices.getUserMedia
Parameters:constraints
Returns:Promise that resolves with a MediaStream object.

const localVideo = document.querySelector("video");
function gotLocalMediaStream(mediaStream) {
  localVideo.srcObject = mediaStream;
}

navigator.mediaDevices
  .getUserMedia({
    video: {
      width: 640,
      height: 480,
      frameRate: 15,
      facingMode: 'enviroment', // use rear camera
      deviceId: deviceId ? {exact: deviceId} : undefined
    },
    audio: false
  })
  .then(gotLocalMediaStream)
  .catch(error => console.log("navigator.getUserMedia error: ", error));

Connection Management

WebRTC uses RTCPeerConnection to manage network connections, media, and data. Key components include SDP (session description), ICE (candidate gathering), and STUN/TURN servers.

SDP (RTCSessionDescription)

SDP describes capabilities such as codecs and transport protocols. It consists of session‑level and media‑level sections as defined in RFC4566.

Session description (session level)
    v= (protocol version)
    o= (originator and session identifier)
    s= (session name)
    c=* (connection information)
    t= (time the session is active)
    a=* (session attributes)

Media description (media level)
    m= (media name and transport address)
    c=* (optional connection information)
    a=* (media attributes)

ICE Candidates (RTCIceCandidate)

ICE gathers possible network paths (host, srflx, relay) to traverse NATs. The onicecandidate event sends each candidate to the remote peer via a signaling server, and addIceCandidate adds received candidates.

API: pc.onicecandidate = eventHandler
pc.onicecandidate = function(event) {
  if (event.candidate) {
    // Send the candidate to the remote peer
  } else {
    // All ICE candidates have been sent
  }
}

API: pc.addIceCandidate
pc.addIceCandidate(candidate).then(_ => {
  // Candidate added successfully
}).catch(e => {
  console.log("Error: Failure during addIceCandidate()");
});

Signaling Server

SDP and ICE information are exchanged through a signaling server, commonly built with socket.io for real‑time messaging.

var express = require("express");
var app = express();
var http = require("http");
const { Server } = require("socket.io");
const httpServer = http.createServer(app);
const io = new Server(httpServer);

io.on("connection", (socket) => {
  console.log("a user connected");
  socket.on("message", (room, data) => {
    socket.to(room).emit("message", room, data);
  });
  socket.on("join", (room) => {
    socket.join(room);
  });
});

Peer‑to‑Peer Connection Flow

1. Peer A creates an RTCPeerConnection with STUN/TURN server configuration.

var pcConfig = {
  iceServers: [
    {
      urls: "turn:stun.al.learningrtc.cn:3478",
      credential: "mypasswd",
      username: "garrylea"
    },
    {
      urls: ["stun:stun.example.com", "stun:stun-1.example.com"]
    }
  ]
};
pc = new RTCPeerConnection(pcConfig);

2. A calls createOffer , sets the local description, and sends the SDP offer via the signaling server.

const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
sendMessage(roomid, offer);

3. B receives the offer, sets it as remote description, creates an answer, and sends it back.

await pc.setRemoteDescription(new RTCSessionDescription(e.data));
const answer = await pc.createAnswer();
sendMessage(roomid, answer);
await pc.setLocalDescription(answer);

4. Both peers exchange ICE candidates (via onicecandidate and addIceCandidate ) until a viable path is found.

5. Media tracks are added with addTrack and received via the ontrack event.

stream.getTracks().forEach(track => {
  pc.addTrack(track, stream);
});

pc.ontrack = (e) => {
  if (e && e.streams) {
    remoteVideo.srcObject = e.streams[0];
  }
};

Bidirectional Data Channel

A data channel can be created with pc.createDataChannel for low‑latency, server‑less communication.

const dc = pc.createDataChannel("chat");
dc.onmessage = receivemsg;
dc.onopen = () => console.log("datachannel open");
dc.onclose = () => console.log("datachannel close");

pc.ondatachannel = (e) => {
  if (!dc) {
    dc = e.channel;
    dc.onmessage = receivemsg;
    dc.onopen = dataChannelStateChange;
    dc.onclose = dataChannelStateChange;
  }
};

NAT and ICE Supplement

NAT devices hide internal addresses; STUN discovers the public address, while TURN relays traffic when direct paths fail. ICE gathers three candidate types: host (local), srflx (server‑reflexive), and relay (TURN).

{
  IP: xxx.xxx.xxx.xxx,
  port: number,
  type: host/srflx/relay,
  priority: number,
  protocol: UDP/TCP,
  usernameFragment: string
}

Result

The tutorial culminates in a functional demo where two browsers can exchange audio, video, and arbitrary data without external plugins.

Demo repository: https://code.byted.org/yuanyuan.wallace/WebRTC/tree/master/demo

References

MDN WebRTC API documentation, GitHub WebRTC‑P2P example, and related RFC4566 specifications.

JavaScriptReal-time CommunicationWebRTCSTUNRTCPeerConnectionTURN
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.