WebRTC - A Simple Video Chat With JavaScript (Part 2)
Tags: webrtc, javascript, chat, stream
Published: 2020-11-26 13:23:19

This post is a second part of the WebRTC implementation to a Video Chat. The previous post we presented the implementation for a video conversation, in this part we will present the implementation of chat.

If you didn't see the previous post click [here](https://dev.to/jeffersonxavier/webrtc-a-simple-video-chat-with-javascript-1686).

## The RTCDataChannel

The RTCDataChannel is a interface from WebRTC resources that provides a channel which can be used for bidirectional peer-to-peer transfers of arbitrary data. The channels are associated with RTCPeerConnection.

We will use the RTCDataChannel to send text messages between connected users.

## Creating de RTCDataChannel

The first step is create a channel using the **createDataChannel**. Whoever initiates the RTCPeerConnection should be also initiate the RTCDataChannel, in our case the RTCDataChannel is initiate when the *Local Connection* are initiate.

The complete start RTCPeerConnection stayed like this:
```javascript
// 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;
    };

    // Start the channel to chat
    localChannel = localConnection.createDataChannel('chat_channel');

    // Function Called When Receive Message in Channel
    localChannel.onmessage = (event) => console.log(`Receive: ${event.data}`);
    // Function Called When Channel is Opened
    localChannel.onopen = (event) => console.log(`Channel Changed: ${event.type}`);
    // Function Called When Channel is Closed
    localChannel.onclose = (event) => console.log(`Channel Changed: ${event.type}`);

    // 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);
      });
  });
```

Focus on the new lines to create the RTCDataChannel:
```javascript
// Start the channel to chat
localChannel = localConnection.createDataChannel('chat_channel');

// Function Called When Receive Message in Channel
localChannel.onmessage = (event) => console.log(`Receive: ${event.data}`);
// Function Called When Channel is Opened
localChannel.onopen = (event) => console.log(`Channel Changed: ${event.type}`);
// Function Called When Channel is Closed
localChannel.onclose = (event) => console.log(`Channel Changed: ${event.type}`);
```

We also declare as variables **localChannel** and **remoteChannel** at then start of the **initConnection** function to store the created channels.

After that, a RTCDataChannel is received to the *Remote Connection* through the **ondatachannel** function. The complete code to *Remote Connection* stayed like this:

```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;
    };

    // Chanel Received
    remoteConnection.ondatachannel = ({ channel }) => {
      // Store Channel
      remoteChannel = channel;

      // Function Called When Receive Message in Channel
      remoteChannel.onmessage = (event) => console.log(`Receive: ${event.data}`);
      // Function Called When Channel is Opened
      remoteChannel.onopen = (event) => console.log(`Channel Changed: ${event.type}`);
      // Function Called When Channel is Closed
      remoteChannel.onclose = (event) => console.log(`Channel Changed: ${event.type}`);
    }

    // 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);
      });
  });
```

Focus on the new lines to receive the RTCDataChannel:
```javascript
// Chanel Received
remoteConnection.ondatachannel = ({ channel }) => {
      // Store Channel
      remoteChannel = channel;

      // Function Called When Receive Message in Channel
      remoteChannel.onmessage = (event) => console.log(`Receive: ${event.data}`);
      // Function Called When Channel is Opened
      remoteChannel.onopen = (event) => console.log(`Channel Changed: ${event.type}`);
      // Function Called When Channel is Closed
      remoteChannel.onclose = (event) => console.log(`Channel Changed: ${event.type}`);
    }
```

## Log Message

In the previous post we created a **div** to present the chat messsages. We will use this area to log all messages received and sended.

First, create the **logMessage** function like this:

```javascript
const logMessage = (message) => {
  const newMessage = document.createElement('div');
  newMessage.innerText = message;
  messagesEl.appendChild(newMessage);
};
```

After, change the channel functions changing the **console.log** functions to **logMessage** function, like this:

```javascript
// Function Called When Receive Message in Channel
localChannel.onmessage = (event) => logMessage(`Receive: ${event.data}`);
    // Function Called When Channel is Opened
    localChannel.onopen = (event) => logMessage(`Channel Changed: ${event.type}`);
    // Function Called When Channel is Closed
    localChannel.onclose = (event) => logMessage(`Channel Changed: ${event.type}`);
```

Make same to **remoteChannel**.

Finally, we created a function for the click of the send button that will send the messages.

```javascript
// Map the 'message-button' click
sendButton.addEventListener('click', () => {
    // GET message from input
    const message = messageInput.value;
    // Clean input
    messageInput.value = '';
    // Log Message Like Sended
    logMessage(`Send: ${message}`);

    // GET the channel (can be local or remote)
    const channel = localChannel || remoteChannel;
// Send message. The other client will receive this message in 'onmessage' function from channel
    channel.send(message);
  });
```

The final result is something like this:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ysofxcqxtrt72khvv8jd.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)

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