Full Stack Developer specializing in React, Node.js, and scalable web solutions.

I am Ujjawal Solanki, a dedicated software developer with expertise in full-stack development, React applications, and Node.js backend services. I create innovative, scalable, and user-friendly web applications.

Video Calling Applications with WebRTC

My First WebRTC Demo Failed While People Were Watching

My first WebRTC demo failed in the most educational way possible: live, in front of other people, while I tried to look calm next to a black remote video box.

The local camera preview worked, which made the failure worse. It gave me enough hope to act confident. The remote stream never arrived.

That awkward silence taught me something important. WebRTC is not hard because the API is evil. It is hard because several moving parts must all line up at the same time.

Why WebRTC feels intimidating at first

You hear new words immediately: signaling, offer, answer, ICE, STUN, TURN, tracks, streams. For beginners, the vocabulary itself can create panic before any code even runs.

My mistake was trying to understand every detail before building anything. The better path was learning the flow one stage at a time.

Most WebRTC pain is not random magic. It is usually a missing step in signaling, permissions, or network traversal.

The sequence that finally made sense to me

Step 1

Get local media working before thinking about the call

If camera and microphone access are unstable, nothing else matters yet. I now treat local media as its own milestone.

const localStream = await navigator.mediaDevices.getUserMedia({
  video: true,
  audio: true
});
localVideoRef.current.srcObject = localStream;
Step 2

Use a signaling server to exchange call messages

WebRTC does not replace signaling. You still need a server path to carry offers, answers, and ICE candidates between peers. Socket.io is a friendly place to start.

socket.on('call:offer', async ({ offer, from }) => {
  await peer.setRemoteDescription(offer);
  const answer = await peer.createAnswer();
  await peer.setLocalDescription(answer);
  socket.emit('call:answer', { answer, to: from });
});
Step 3

Add tracks before creating the offer

I lost time here because I created an offer too early. If the peer connection does not already know about local tracks, the negotiation can look oddly incomplete.

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

const offer = await peer.createOffer();
await peer.setLocalDescription(offer);
Step 4

Handle remote tracks and attach them to the UI

Receiving media is not just a networking event. The UI must know where to render the stream.

peer.ontrack = (event) => {
  const [remoteStream] = event.streams;
  remoteVideoRef.current.srcObject = remoteStream;
};
Step 5

Respect real-world networks with STUN and TURN

This is the stage many demos avoid because it works on friendly networks and local testing. Real users eventually join from restrictive networks and the call mysteriously fails.

const peer = new RTCPeerConnection({
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    {
      urls: 'turn:turn.yourserver.com:3478',
      username: 'user',
      credential: 'pass'
    }
  ]
});

Troubleshooting the black screen problem

Permissions were never granted

If the browser blocks mic or camera access, everything downstream becomes confusing. Check permissions first.

Offer and answer exchanged, but ICE candidates did not

This is one of the most common causes of false hope. Signaling looks alive, but the peers still cannot find a usable route.

HTTPS was missing in a real environment

Many media APIs behave best or only on secure origins. Localhost can hide this for a while.

Tracks were not cleaned up after the call

I once ended a call in the UI but left media tracks running. The app felt haunted because the camera light kept glowing.

When debugging WebRTC, log each stage separately: media access, offer creation, answer receipt, ICE candidate flow, remote track event.

What building video calling taught me beyond video

WebRTC taught me patience and sequencing. It punished rushing in a very honest way. Each missing step surfaced as a real symptom.

Once I respected the order of operations, the system felt less magical and more mechanical. That is when I started trusting myself with it.

If this helped you, save it for later. And if you are stuck on a MERN feature, you can always reach out to me.