WebRTC - A Simple Video Chat With JavaScript (Part 2)

jeffersonxavier

Jefferson Xavier

Posted on November 26, 2020

WebRTC - A Simple Video Chat With JavaScript (Part 2)

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.

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:

// 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);
      });
  });
Enter fullscreen mode Exit fullscreen mode

Focus on the new lines to create the RTCDataChannel:

// 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}`);
Enter fullscreen mode Exit fullscreen mode

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:

// 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);
      });
  });
Enter fullscreen mode Exit fullscreen mode

Focus on the new lines to receive the RTCDataChannel:

// 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}`);
    }
Enter fullscreen mode Exit fullscreen mode

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:

const logMessage = (message) => {
  const newMessage = document.createElement('div');
  newMessage.innerText = message;
  messagesEl.appendChild(newMessage);
};
Enter fullscreen mode Exit fullscreen mode

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

// 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}`);
Enter fullscreen mode Exit fullscreen mode

Make same to remoteChannel.

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

// 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);
  });
Enter fullscreen mode Exit fullscreen mode

The final result is something like this:

Alt Text

Reference

WebRTC API

WebRTC in real world

Next Steps

You can see all code in GitHub

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

💖 💪 🙅 🚩
jeffersonxavier
Jefferson Xavier

Posted on November 26, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related