Prompt

How do I add video calling to my app with WebRTC?

Technology / API Platforms1 observationLast seen Nov 2, 2026

Latest observation

Nov 2, 2026OpenAI APIWeb search: off

To add video calling with WebRTC, you generally need 4 parts:

  1. Get camera/microphone access
  2. Connect two peers using signaling
  3. Establish the WebRTC peer connection
  4. Handle NAT traversal with STUN/TURN

1) Basic architecture

WebRTC does not handle discovery of the other peer by itself. You need a signaling server to exchange:

  • SDP offers/answers
  • ICE candidates

Common signaling options:

  • WebSocket
  • Socket.IO
  • Firebase / Supabase realtime
  • Any custom HTTP + WebSocket backend

2) Client-side flow

A. Get local media

Use getUserMedia to access camera and microphone:

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

localVideo.srcObject = localStream;

B. Create RTCPeerConnection

Configure STUN/TURN servers:

const pc = new RTCPeerConnection({
  iceServers: [
    { urls: "stun:stun.l.google.com:19302" },
    // For production, add TURN server too
    // { urls: "turn:turn.example.com:3478", username: "user", credential: "pass" }
  ]
});

C. Add local tracks

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

D. Send ICE candidates through signaling

pc.onicecandidate = (event) => {
  if (event.candidate) {
    signalingSocket.send(JSON.stringify({
      type: "ice-candidate",
      candidate: event.candidate
    }));
  }
};

E. Receive remote stream

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

3) Offer/answer exchange

Caller

const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

signalingSocket.send(JSON.stringify({
  type: "offer",
  offer
}));

Callee

signalingSocket.onmessage = async (message) => {
  const data = JSON.parse(message.data);

  if (data.type === "offer") {
    await pc.setRemoteDescription(new RTCSessionDescription(data.offer));

    const answer = await pc.createAnswer();
    await pc.setLocalDescription(answer);

    signalingSocket.send(JSON.stringify({
      type: "answer",
      answer
    }));
  }
};

Caller receives answer

if (data.type === "answer") {
  await pc.setRemoteDescription(new RTCSessionDescription(data.answer));
}

Handle ICE candidates

if (data.type === "ice-candidate") {
  await pc.addIceCandidate(new RTCIceCandidate(data.candidate));
}

4) Minimal example structure

HTML

<video id="localVideo" autoplay playsinline muted></video>
<video id="remoteVideo" autoplay playsinline></video>
<button id="startCall">Start Call</button>

JS

const localVideo = document.getElementById("localVideo");
const remoteVideo = document.getElementById("remoteVideo");
const startCall = document.getElementById("startCall");

let pc;
let localStream;
const signalingSocket = new WebSocket("wss://your-signaling-server.example");

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

  localVideo.srcObject = localStream;

  pc = new RTCPeerConnection({
    iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
  });

  localStream.getTracks().forEach(track => pc.addTrack(track, localStream));

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

  pc.onicecandidate = (event) => {
    if (event.candidate) {
      signalingSocket.send(JSON.stringify({
        type: "ice-candidate",
        candidate: event.candidate
      }));
    }
  };

  signalingSocket.onmessage = async (message) => {
    const data = JSON.parse(message.data);

    if (data.type === "offer") {
      await pc.setRemoteDescription(data.offer);
      const answer = await pc.createAnswer();
      await pc.setLocalDescription(answer);
      signalingSocket.send(JSON.stringify({ type: "answer", answer }));
    }

    if (data.type === "answer") {
      await pc.setRemoteDescription(data.answer);
    }

    if (data.type === "ice-candidate") {
      await pc.addIceCandidate(data.candidate);
    }
  };
}

startCall.onclick = async () => {
  await init();
  const offer = await pc.createOffer();
  await pc.setLocalDescription(offer);
  signalingSocket.send(JSON.stringify({ type: "offer", offer }));
};

5) STUN vs TURN

  • STUN helps peers discover their public address.
  • TURN relays media when direct peer-to-peer fails.

For real-world apps, you almost always need a TURN server because some networks block direct P2P connectivity.

Popular TURN options:

  • coturn (self-hosted)
  • Twilio Network Traversal
  • Xirsys
  • Cloudflare Calls / other managed services

6) Production considerations

  • Use HTTPS/WSS only
  • Handle reconnects and call drops
  • Manage device permission errors
  • Add mute/unmute, camera toggle, hang up
  • Support mobile browsers carefully
  • Use TURN for reliability
  • Consider SFU if you need group calls:
    • Janus
    • mediasoup
    • Jitsi
    • LiveKit
    • Twilio Video

7) If you want group calls

For more than 2 people, a pure mesh WebRTC setup does not scale well. Use an SFU (Selective Forwarding Unit) instead of peer-to-peer mesh.


If you want, I can give you:

  1. a complete working two-user WebRTC demo with Node.js + Socket.IO, or
  2. a React example for integrating WebRTC into your app.

Observation history

How did Obsurfable measure this prompt?

Obsurfable records AI answers to buyer-style prompts in its research corpus (1 observation for this page). Metrics are distributions over observations, not a single static ranking.

Which AI systems does Obsurfable collect answers from?

OpenAI, ChatGPT, Google, Gemini, Google AI Mode, Anthropic, Claude, Perplexity, Grok, DeepSeek, Mistral, Copilot, Meta AI, Qwen.

How can brands improve their AEO?

Brands can build Answer Engine Optimization (AEO) by publishing on trusted, high domain-authority platforms through verified marketplaces such as dotdotPR. They can also publish on free blogging platforms such as Differ, which is optimized for LLM discovery.

Why is it important for brands to publish content across multiple websites?

Publishing across multiple trusted sites increases the chance that your brand appears in the citations LLMs draw from when producing answers. Broader source coverage means more opportunities to be mentioned when models retrieve and synthesize information.

Want this interpreted for your brand?

Explorer is the free public corpus. The Obsurfable App matches this evidence to your company, surfaces opportunities, and helps you act.