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

Jeffersonx Xavier
4 min readNov 26, 2020

--

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

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

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

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

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

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

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

The final result is something like this:

Reference

WebRTC in real world

Next Steps

You can see all code in GitHub

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

Originally published at https://dev.to on November 26, 2020.

--

--

Jeffersonx Xavier

Software Engineer and Full-Stack Developer. A bigger Javascript enthusiast.