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:
Table of Contents
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-app
npm 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:
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
Package.json should contain a reference to the apirtc library.
If you run a npm start
command, you should obtain the following:
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/.
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):
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 π
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.
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
- Go deeper into ApiRTC with the Developer Portal
- Discover what you can do with the video and audio ApiRTC APIs by checking the code examples on Github.