Understanding WebRTC

Written by jorgeortega | Published 2020/08/18
Tech Story Tags: webrtc | html5 | javascript | communication | video-streaming | streaming | realtime | api

TLDR WebRTC stands for Web Real Time Communication. It supports video, voice, and generic data to be sent between peers. The API is actually not so big and complicated as it sounds. The MDN website is, as always, very complete https://developer.mozilla.org/en-US/docs/Glossary/WebRTC.org. In webrtc.org we can find many useful samples of the API. In a basic call, we are going to establish a connection between two peers, adding each other ICECandidates, set local audio/video tracks, create a connection offer from peer 1 and create an answer.via the TL;DR App

What is WebRTC?
WebRTC stands for Web Real Time Communication. From webrtc.org:
"With WebRTC, you can add real-time communication capabilities to your application that works on top of an open standard. It supports video, voice, and generic data to be sent between peers, allowing developers to build powerful voice and video communication solutions"
The API is actually not so big and complicated as it sounds. The MDN website is, as always, very complete https://developer.mozilla.org/en-US/docs/Glossary/WebRTC.
We have 3 main parts:
  1. getUserMedia(): sends a signal to the browser to ask the user for permission and returns the "stream" of audio/video/data
  2. RTCPeerConnection: two participants are called peers, local and remote. Not necessarily physically remote, can be two browsers in the same computer.
  3. RTCDataChannel: two peers connect via a channel in a bidirectional way to transfer data. A peer can have up to 65,534 data channels (depends on the browser)
In webrtc.org we can find many useful samples https://webrtc.github.io/samples/
Let's dissect a basic peer-to-peer connection.
First, we will need two video elements, one to see the other person and another one to see ourselves:
<video id="localVideo" playsinline autoplay muted></video>

<video id="remoteVideo" playsinline autoplay></video>
Then the controls to start, call and hangup:
<button id="startButton">Start</button>
<button id="callButton">Call</button>
<button id="hangupButton">Hang Up</button>
Now we can start using the WebRTC API to handle all the peer-to-peer connection events. First we set some global variables, get GUI elements and add click handlers:
const startButton = document.getElementById("startButton");
const callButton = document.getElementById("callButton");
const hangupButton = document.getElementById("hangupButton");

startButton.addEventListener("click", start);
callButton.addEventListener("click", call);
hangupButton.addEventListener("click", hangup);

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

let localStream;
let pc1;
let pc2;
const offerOptions = {
  offerToReceiveAudio: 1,
  offerToReceiveVideo: 1,
};
When we click start, basically we want to ask the user for permission to use the camera and microphone with getUserMedia():
async function start() {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({
      audio: true,
      video: true,
    });

    localVideo.srcObject = stream;
    localStream = stream;
  } catch (e) {
    alert(`getUserMedia() error: ${e.name}`);
  }
}
When starting a call, we are going to establish a connection between two peers, adding each other ICECandidates, set local audio/video tracks, create a connection offer from peer 1 and create an answer from peer 2:
async function call() {
  const configuration = {};

  pc1 = new RTCPeerConnection(configuration);
  pc1.addEventListener("icecandidate", (e) => onIceCandidate(pc1, e));

  pc2 = new RTCPeerConnection(configuration);
  pc2.addEventListener("icecandidate", (e) => onIceCandidate(pc2, e));
  pc2.addEventListener("track", gotRemoteStream);

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

  try {
    await pc1.createOffer(offerOptions);
    await onCreateOfferSuccess()
  } catch (e) {
    onCreateSessionDescriptionError(e);
  }
}

async function onCreateOfferSuccess() {
  try {
    await pc2.createAnswer();
  } catch (e) {
    onCreateSessionDescriptionError(e);
  }
}

function onCreateSessionDescriptionError(error) {
  console.log(`Failed to create session description: ${error.toString()}`);
}
From the code above, we notice that we need an RTCIceCandidate, it represents a Internet Connectivity Establishment.
It describes all under-the-hood logic needed for two peers to communicate, protocols, routing, error correction, etc. Here we will add the candidates on both sides:
function getName(pc) {
  return pc === pc1 ? "pc1" : "pc2";
}

function getOtherPc(pc) {
  return pc === pc1 ? pc2 : pc1;
}

async function onIceCandidate(pc, event) {
  try {
    await getOtherPc(pc).addIceCandidate(event.candidate);
  } catch (e) {
    onAddIceCandidateError(pc, e);
  }
}

function onAddIceCandidateError(pc, error) {
  console.log(
    `${getName(pc)} failed to add ICE Candidate: ${error.toString()}`
  );
}
After adding ICE candidates, we added a listener to the "track" event. We will add the event stream to the srcObject of the video element:
function gotRemoteStream(e) {
  if (remoteVideo.srcObject !== e.streams[0]) {
    remoteVideo.srcObject = e.streams[0];
  }
}
This should give us video from both sides.
Finally, to hangup a call, we close the peer connections and set them to null:
function hangup() {
  pc1.close();
  pc2.close();
  pc1 = null;
  pc2 = null;
}
I hope I simplified enough the webrtc.org basic peer connection example
so you can start creating awesome video call applications. Of course for a production ready service, we will have to consider a lot more.
Happy coding.

Written by jorgeortega | Software Engineer from Costa Rica, living in Berlin
Published by HackerNoon on 2020/08/18