Build a custom video chat with ReactJS and ApiRTC

Get a reactJS video communication application

Build a custom video chat with ReactJS and ApiRTC

Last Updated on December 30, 2022

Build a custom video chat with ReactJS and ApiRTC from scratch in 15min.

 

This post is part of the ApiRTC Wildcard series on how to build video communication applications using ApiRTC’s real-time video and audio APIs.

 

ApiRTC’s Javascript library, apirtc.js, enables you to add video and audio communication capabilities to any web application.

In this article, you will go through the steps to use the apirtc.js library to build a real-time video chat ReactJS application.

Here is what the final app will look like:

Get a reactJS video communication application


Full source code of this ReactJs video conference bootstrap

You can find the source code of this tutorial on the ApiRTC GitHub space: https://github.com/ApiRTC/apirtc-react

If you’re curious about creating a video chat app but without ReactJS, you can read our How to build a simple web app with apirtc.js article.


Get yourself ready to build your first custom video chat with ReactJS and ApiRTC

ApiRTC Cloud account set up and ready

This tutorial requires you to have an active account on ApiRTC Cloud: Create an ApiRTC Cloud account.

Once you are connected, you can get your API key from your dashboard or in the Credential section.

NodeJS and NPM installed

We use NPM to install ReactJS and all the dependencies, which use NodeJS to run. You must have NodeJS and NPM installed on your machine in their latest LTS versions.

This tutorial uses Linux-based commands. Please tell us if you would like other OS to be documented as well.


Setting up the tutorial environment

Create a folder containing a ReactJS boilerplate using the create-react-appnpm package

Type the npx command to temporarly download and run the create-react-app package to generate a ReactJS boilerplate application with just the minimum:

npx create-react-app my_apirtc_react_app
cd my_apirtc_react_app

You sould get something like below:npm install command for apirc

Add the apirtc.js video and audio library module to the ReactJS app

Run the following command in your project folder:

npm install @apirtc/apirtc

You should obtain something similar to the following in your package.json file

get video communication in your package.json
Package.json should contain a reference to the apirtc library.

If you run a npm start command, you should obtain the following:

reactjs start new video app
Your ReactJS application is started!

You are now ready to start implementing your first video communication app using ReactJS and ApiRTC library.


Create the Conversation component

ReactJS manages the rendering life cycle of the application by using Component. We create the Conversation component responsible for

  • recording and managing the state of the video conversation
  • rendering the HTML markup that will contain the display of local and remote video streams.
cd src
mkdir Components
cd Components
touch ConversationComponent.jsx

Make the component render HTML

In the ConversationComponent.jsx file insert the following:

import { UserAgent } from '@apirtc/apirtc';
import React, { useState, useRef } from 'react';

export default function ConversationComponent() 
  return (
    <div>
        <h2>Remote videos</h2>
        <div id="remote-container">
          {/* <!-- This is where the remote video streams will be added --> */}
        </div>
        <div id="local-container">
          <h2>Local video</h2>
          <p><span id="local-stream-id"></span></p>
          {/* <!-- This is where we are going to display our local video stream --> */}
          <video id="local-video-stream" autoPlay muted></video>
        </div>
      </div>
    </div>)
}

Remove everything inside of the <header> markup of the App.js file and replace it by a <ConversationComponent> markup and add the import at the top of the file.

It should now look like this:

import ConversationComponent from "./Components/ConversationComponent"

import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <ConversationComponent></ConversationComponent>
      </header>
    </div>
  );
}

export default App;

Start the app with the npm start command and navigate to http://localhost:3000/.

reactJS video chat application layout

Hooray, you’ve got your app layout!

You might have some warning prompted while starting the app: no worries, delete logo.svg import and all will be fine.


Add video communication capability to the ReactJS app

ConversationComponent will implement the logic described as followed (click to get a detailed view):

videio communication application workflow

Add the basic video communication workflow

Declare a startConversation() method in the ConversationComponent function, which will handle the whole connection process.

Be mindful to replace #YOUR_OWN_API_KEY# with your own API key to be found at https://cloud.apirtc.com/enterprise/api

const conversationRef = useRef(null);
const [conversationName, setConversationName] = useState("")

 var startConversation = function () {
    var localStream = null;

    /**
     * Get your free account on https://cloud.apirtc.com/ 
     * and replace the value below with your own apikey value 
     * to be found at https://cloud.apirtc.com/enterprise/api
     */

    var apikey = "#YOUR_OWN_API_KEY#"

    //Configure the User Agent using the apikey.
    var ua = new UserAgent({
      uri: 'apiKey:' + apikey
    })

    //Connect the UserAgent and get a session
    ua.register().then((session) => {

      var conversationName = "CONVERSATION_NAME"

      const conversation = session.getOrCreateConversation(conversationName, { meshOnlyEnabled: true })

      setConversationName(conversation.getName())

      conversationRef.current = conversation

      //Instantiate a local video stream object
      ua.createStream({
        constraints: {
          audio: true,
          video: true
        }
      })
        .then((stream) => {

          // Save local stream in a variable accessible to eventhandlers
          localStream = stream;

          //Display the local video stream
          stream.attachToElement(document.getElementById('local-video-stream'));
          document.getElementById('local-stream-id').innerHTML = ua.getUsername()

          //Join the conversation
          conversation.join()
            .then((response) => {

              //Publish the local stream to the conversation
              conversation
                .publish(localStream)
                .then((stream) => {
                  console.log("Your local stream is published in the conversation", stream);
                })
                .catch((err) => {
                  console.error("publish error", err);
                });
            }).catch((err) => {
              console.error('Conversation join error', err);
            });

        }).catch((err) => {
          console.error('create stream error', err);
        });
    });
  }

For testing purposes, we are going to call the startConversation method when we click on the “Start the Conversation” button. Add the following button markup rendered by the ConversationComponent:

<div>
   <button id="startConversation" onClick={startConversation}>Start Conversation</button>
</div>
<div>
   <p>Conversation Name: <span id="conversationNameLabel">{conversationName}</span></p> 
</div>

npm start in your project folder and navigate to the http://localhost:3000 address.

Clap your hands if you see yourself 👏

reactJS video API application

Add participants’ remote video streams to your application

We are halfway to finalizing our app! Keep up the good work buddy. 👍

Here is the logic to manage the remote video streams:

  • Each time a new stream is detected 👉 subscribe to every event thrown by this stream
  • Each time a new stream is added to the conversation 👉 add the video stream to the interface
  • Each time a new stream is removed from the conversation 1. 👉 remove the video stream from the interface

We are going to use Conversation.streamListChanged, Conversation.streamAdded and Conversation.streamRemoved events to trigger these actions.

Insert event handlers

Declare event handler functions as variables of the ConversationComponent function:

//streamListchanged: subscribe to new remote streams published in the conversation and get future events triggered by this stream
  var onStreamListChangedHandler = function (streamInfo) {
    if (streamInfo.listEventType === 'added' && streamInfo.isRemote) {

      if (conversationRef.current)
        conversationRef.current.subscribeToStream(streamInfo.streamId)
          .then((stream) => {
            console.log('subscribeToStream success', streamInfo);
          }).catch((err) => {
            console.error('subscribeToStream error', err);
          });
    }
  }

  //streamAdded: Display the newly added stream
  var onStreamAddedHandler = function (stream) {
    if (stream.isRemote) {
      stream.addInDiv('remote-container', 'remote-media-' + stream.streamId, {}, false);
    }
  }

  //streamRemoved: Remove the participant's display from the UI
  var onStreamRemovedHandler = function (stream) {
    if (stream.isRemote) {
      stream.removeFromDiv('remote-container', 'remote-media-' + stream.streamId)
    }
  }

Declare video streams listeners

And then, declare the listeners just before the ua.createStream method call in the startConversation function:

conversation.on("streamListChanged", onStreamListChangedHandler)
conversation.on("streamAdded", onStreamAddedHandler)
conversation.on("streamRemoved", onStreamRemovedHandler)

 Open 2 tabs with the app running, you should see yourself twice!

Turn off your sound output or microphone as it creates feedback and a shrieking sound.

 

screenshot of video chat application
If you see yourself twice, you’re on a good way!

To sum up here is what ConversationComponent.jsx should look like at this step:

import { UserAgent } from '@apirtc/apirtc';
import React, { useState, useRef } from 'react';

export default function ConversationComponent() {

  const conversationRef = useRef(null);
  const [conversationName, setConversationName] = useState("")

  //streamListchanged: subscribe to new remote stream published in the conversation and get future events triggered by this stream
  const onStreamListChangedHandler = function (streamInfo) {
    if (streamInfo.listEventType === 'added' && streamInfo.isRemote) {

      if (conversationRef.current)
        conversationRef.current.subscribeToStream(streamInfo.streamId)
          .then((stream) => {
            console.log('subscribeToStream success', streamInfo);
          }).catch((err) => {
            console.error('subscribeToStream error', err);
          });
    }
  }

  //streamAdded: Display the newly added stream
  const onStreamAddedHandler = function (stream) {
    if (stream.isRemote) {
      stream.addInDiv('remote-container', 'remote-media-' + stream.streamId, {}, false);
    }
  }

  //streamRemoved: Remove the participant's display from the UI
  const onStreamRemovedHandler = function (stream) {
    if (stream.isRemote) {
      stream.removeFromDiv('remote-container', 'remote-media-' + stream.streamId)
    }
  }

  const startConversation = function () {
    var localStream = null;

    /**
     * Get your free account on https://cloud.apirtc.com/ 
     * and replace the value below with your own apikey value 
     * to be found at https://cloud.apirtc.com/enterprise/api
     */

    const apikey = "myDemoApiKey" //"#YOUR_OWN_API_KEY#"

    //Configure the User Agent using the apikey.
    const ua = new UserAgent({
      uri: 'apiKey:' + apikey
    })

    //Connect the UserAgent and get a session
    ua.register().then((session) => {

      const conversationName = "CONVERSATION_NAME"

      const conversation = session.getOrCreateConversation(conversationName, { meshOnlyEnabled: true })

      setConversationName(conversation.getName())

      conversationRef.current = conversation

      conversation.on("streamListChanged", onStreamListChangedHandler)
      conversation.on("streamAdded", onStreamAddedHandler)
      conversation.on("streamRemoved", onStreamRemovedHandler)

      //Instantiate a local video stream object
      ua.createStream({
        constraints: {
          audio: true,
          video: true
        }
      })
        .then((stream) => {

          // Save local stream in a constiable accessible to eventhandlers
          localStream = stream;

          //Display the local video stream
          stream.attachToElement(document.getElementById('local-video-stream'));
          document.getElementById('local-stream-id').innerHTML = ua.getUsername()


          //Join the conversation
          conversation.join()
            .then((response) => {

              conversation
                .publish(localStream)
                .then((stream) => {
                  console.log("Your local stream is published in the conversation", stream);
                })
                .catch((err) => {
                  console.error("publish error", err);
                });
            }).catch((err) => {
              console.error('Conversation join error', err);
            });

        }).catch((err) => {
          console.error('create stream error', err);
        });
    });
  }

  return (
    <div>
      <div>
         <button id="startConversation" onClick={startConversation}>Start Conversation</button>
      </div>
      <div>
        <p>Conversation Name: <span id="conversationNameLabel">{conversationName}</span></p>
      </div>
      <div>
        <h2>Remote videos</h2>
        <div id="remote-container">
          {/* <!-- This is where the remote video streams will be added --> */}
        </div>
        <div id="local-container">
          <h2>Local video</h2>
          <p><span id="local-stream-id"></span></p>
          {/* <!-- This is where we are going to display our local video stream --> */}
          <video id="local-video-stream" autoPlay muted></video>
        </div>
      </div>
    </div>)
}

 

Great folks, you finised to build a custom video chat with ReactJS and ApiRTC!

Good work, you can give yourself a treat! 🎉


Going further