WebRTC - A Simple Video Chat With JavaScript (Part 1)
Tags: webrtc, javascript, stream, chat
Published: 2020-11-11 16:07:24

The **WebRTC** (Web Real-Time Communications) is a technology with a set of features that allow an user get audio/video medias and transmit this information at a peer to peer communication. It's also possible send any data like text or files with this connection.

This post provides a tutorial to implement a simple video sharing and whit chat without use any libraries or plugins beyond of resources from WebRTC.


## Project Structure

This project consist of a server that works like an access point to clients start a web communication. WebSocket is used so that clients can know each other.

The client is a simple HTML to get a Video/Audio Stream and a input to send chat messages. The WebRTC communication is implemented in a Javascript file imported by this HTML.


## The WebRTC Resources

* **MediaStream:** Represents a stream of media content with tracks to audio and video. You can get a MediaStream object using the *navigator.mediaDevices.getUserMedia()* function.

* **RTCPeerConnection:** Represents a connection between two peers. It's used to send the stream between clients.

* **RTCDataChannel:** Represents a bidirectional data channel between two pairs of a connection. It's used to send chat messages between clients.

## Show me the code

Let's start with the server code. First we go start a NodeJS project.


```
yarn init -y
```

Install the necessary dependencies. **Express** to create a server and **socket.io** to enables the WebSocket communication.

```
yarn add express socket.io
```

Create **server.js** to start our server and put the follow code:

```javascript
const express = require('express');
const socketio = require('socket.io');
const cors = require('cors');
const http = require('http');

// Create server
const app = express();
const server = http.Server(app);

// Enable Cors to Socket IO
app.use(cors());

// Init Socket IO Server
const io = socketio(server);

// Called whend a client start a socket connection
io.on('connection', (socket) => {

});

// Start server in port 3000 or the port passed at "PORT" env variable
server.listen(process.env.PORT || 3000,
  () => console.log('Server Listen On: *:', process.env.PORT || 3000));

```

The initial project structure should be something like:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ztdqtmz8q5y4u6rckmhk.png)

## The WebSocket Structure

The objective of websocket is make the client knows each other no WebRTC connection.

The WebRTC connection is established in some steps describe bellow. All this steps are explained in client implementation section.

1. Create a **RTCPeerConnection Instance**;
2. Create a **Offer** to connection;
3. Send a **Answer** to offer request;
4. **Signaling** between clients.

So, to implement this it's necessary add some events to socket.

The first step is send to myself the others users connected to start the RTCPeerConnection with each them. After that, we have events to establish the connection with all steps describe above.

Below we have the complete code to this implementation.

```javascript
// Array to map all clients connected in socket
let connectedUsers = [];

// Called whend a client start a socket connection
io.on('connection', (socket) => {
  // It's necessary to socket knows all clients connected
  connectedUsers.push(socket.id);

  // Emit to myself the other users connected array to start a connection with each them
  const otherUsers = connectedUsers.filter(socketId => socketId !== socket.id);
  socket.emit('other-users', otherUsers);

  // Send Offer To Start Connection
  socket.on('offer', (socketId, description) => {
    socket.to(socketId).emit('offer', socket.id, description);
  });

  // Send Answer From Offer Request
  socket.on('answer', (socketId, description) => {
    socket.to(socketId).emit('answer', description);
  });

  // Send Signals to Establish the Communication Channel
  socket.on('candidate', (socketId, signal) => {
    socket.to(socketId).emit('candidate', signal);
  });

  // Remove client when socket is disconnected
  socket.on('disconnect', () => {
    connectedUsers = connectedUsers.filter(socketId => socketId !== socket.id);
  });
});
```

## The Client Code

Fist create a folder with name **public** and add the files **index.html** and **main.js**. The final project structure should look like this:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7ietduj3267ujko7vnvv.png)

* **HML Code:**

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>WebRTC Example</title>

  <style>
    #video-grid {
      display: none;
      grid-template-columns: repeat(auto-fill, 400px);
      grid-auto-rows: 400px;
    }

    video {
      width: 100%;
      height: 100%;
    }
  </style>

  <script src="/socket.io/socket.io.js"></script>
  <script src="/main.js" type="module"></script>
</head>
<body>
  <h1>Hello!</h1>

  <!-- My Video and Remote Video from connection -->
  <div id="video-grid">
    <video playsinline autoplay muted id="local-video"></video>
    <video playsinline autoplay id="remote-video"></video>
  </div>

  <!-- Input to send messages -->
  <div>
    <span style="font-weight: bold">Message: </span>
    <input type="text" id="message-input" title="Message to Send!">
    <button id="message-button">Send</button>
  </div>

  <!-- Area to Print Images -->
  <div class="messages"></div>
</body>
</html>
```

In **main.js** file the first step is start a **MediaStream**, like this:

```javascript
console.log('Main JS!');

// Map All HTML Elements
const videoGrid = document.getElementById('video-grid');
const messagesEl = document.querySelector('.messages');
const messageInput = document.getElementById('message-input');
const sendButton = document.getElementById('message-button');
const localVideo = document.getElementById('local-video');
const remoteVideo = document.getElementById('remote-video');

// Open Camera To Capture Audio and Video
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
  .then(stream => {
    // Show My Video
    videoGrid.style.display = 'grid';
    localVideo.srcObject = stream;

    // Start a Peer Connection to Transmit Stream
    initConnection(stream);
  })
  .catch(error => console.log(error));
```

The result is something like this, with your video in *local-video* area.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/is6x0j593bhiyx4o9t0f.png)
 

The next steps are start a socket connection and init RTCPeerConnectin to each other users connected. When receive **other-users** socket event, the client will initiate a connection with each them.

```javascript
const initConnection = (stream) => {
  const socket = io('/');
  let localConnection;
  let remoteConnection;

  // Start a RTCPeerConnection to each client
  socket.on('other-users', (otherUsers) => {
    // Ignore when not exists other users connected
    if (!otherUsers || !otherUsers.length) return;

    const socketId = otherUsers[0];

    // Ininit peer connection
    localConnection = new RTCPeerConnection();

    // Add all tracks from stream to peer connection
    stream.getTracks().forEach(track => localConnection.addTrack(track, stream));

    // Send Candidtates to establish a channel communication to send stream and data
    localConnection.onicecandidate = ({ candidate }) => {
      candidate && socket.emit('candidate', socketId, candidate);
    };
  
    // Receive stream from remote client and add to remote video area
    localConnection.ontrack = ({ streams: [ stream ] }) => {
      remoteVideo.srcObject = stream;
    };

    // Create Offer, Set Local Description and Send Offer to other users connected
    localConnection
      .createOffer()
      .then(offer => localConnection.setLocalDescription(offer))
      .then(() => {
        socket.emit('offer', socketId, localConnection.localDescription);
      });
  });
}
```

**IMPORTANT:** In real world the RTCPeerConnection must be initalized with configurations to *iceServers* with **STUN** and **TURN** servers, this is necessary to get the real IP to internet connection and avoid NAT blocks in network. See more about this in [RTCPeerConnection](https://developer.mozilla.org/pt-BR/docs/Web/API/RTCPeerConnection) and [WebRTC in real world](https://www.html5rocks.com/en/tutorials/webrtc/infrastructure/)

Continuing our tutorial, now the other client will receive the **offer** request and must create a RTCPeerConnection with your **answer**.

```javascript
// Receive Offer From Other Client
socket.on('offer', (socketId, description) => {
    // Ininit peer connection
    remoteConnection = new RTCPeerConnection();

    // Add all tracks from stream to peer connection
    stream.getTracks().forEach(track => remoteConnection.addTrack(track, stream));

    // Send Candidtates to establish a channel communication to send stream and data
    remoteConnection.onicecandidate = ({ candidate }) => {
      candidate && socket.emit('candidate', socketId, candidate);
    };
  
    // Receive stream from remote client and add to remote video area
    remoteConnection.ontrack = ({ streams: [ stream ] }) => {
      remoteVideo.srcObject = stream;
    };

    // Set Local And Remote description and create answer
    remoteConnection
      .setRemoteDescription(description)
      .then(() => remoteConnection.createAnswer())
      .then(answer => remoteConnection.setLocalDescription(answer))
      .then(() => {
        socket.emit('answer', socketId, remoteConnection.localDescription);
      });
  });
```

Lastly, the first client receive the **answer** and set the **Remote Description**. So, start the send candidates to create a communication channel to send the stream.

```javascript
// Receive Answer to establish peer connection
socket.on('answer', (description) => {
  localConnection.setRemoteDescription(description);
});

// Receive candidates and add to peer connection
socket.on('candidate', (candidate) => {
  // GET Local or Remote Connection
  const conn = localConnection || remoteConnection;
  conn.addIceCandidate(new RTCIceCandidate(candidate));
});
```

The final result is something looks like the image below with showing Local and Remote videos.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/gpfc9fhwec94inlx5qmj.png)

## Reference

[WebRTC API](https://developer.mozilla.org/pt-BR/docs/Web/API/WebRTC_API)

[WebRTC in real world](https://www.html5rocks.com/en/tutorials/webrtc/infrastructure/)

## Next Steps

You can see all code in [GitHub](https://github.com/jeffersonxavier/webrtc-example)

Follow the [next post](https://dev.to/jeffersonxavier/webrtc-a-simple-video-chat-with-javascript-part-2-6fk) to build sending chat messages and complete this tutorial.

Thanks for your reading. Please, leave your comment with your contribution.