Development Monday August 16, 2021

How to use AWS Chime and ReactJS to create a video calling POC

The use of video calling technologies has surged tremendously as we see remote business and remote socializing becoming increasingly common. We can see that video chat apps are gaining a lot of traction, both in terms of commercial and personal use.

In consideration of this, here is a blog post that describes the simplest, cost-effective, and customizable solution for developing a web-based video chat application.

Amazon Chime is a communications solution that allows users to meet, chat, and make business calls both inside and outside the business, all through one app. The Amazon Chime allows developers to access the same communications infrastructure and services that run Amazon Chime to add voice, video, and screen sharing capabilities to their applications. Here is all you need to know about Amazon Chime: https://aws.amazon.com/chime/

Let’s get into it!

This article will make sure to explain how to use the AWS SDK and Chime to create a basic video chat with express as the server and ReactJS as the client.

The process includes the following steps:

  1. Creating a meeting
  2. Creating a meeting attendee
  3. Running the server
  4. Creating the client

The above two steps come under server-side for which the following npm packages are considered:  

Now, it is necessary to build a set-up before creating a meeting which includes the following

(1) Creating a meeting

Here is the server code for creating a meeting.

const express = require('express')
const app = express()
const AWS = require('aws-sdk')
const { v4: uuid } = require('uuid')
const cors = require('cors')
const region = 'us-east-1'

const chime = new AWS.Chime({ region })
chime.endpoint = new AWS.Endpoint(
    'https://service.chime.aws.amazon.com/console'
)

app.get('/meeting', cors(), async (req, res) => {
    const response = {}
    try {
        response.meetingResponse = await chime
            .createMeeting({
                ClientRequestToken: uuid(),
                MediaRegion: region,
            })
            .promise()
    } catch (err) {
        res.send(err)
    }

    res.send(response)
})

app.listen(5000, () => console.log(`Video calling POC server listening at http://localhost:5000`))

Let’s take a look at each section to see what’s going on.

Once the imports and setting of the AWS region are done, it is required to set up an instance for AWS Chime.

const chime = new AWS.Chime({ region })
chime.endpoint = new AWS.Endpoint(
    'https://service.chime.aws.amazon.com/console'
)

Now, this completes the setting of the chime region as well as the endpoint.

The next step comes with creating an endpoint.

app.get('/meeting', cors(), async (req, res) => {

The above line shows two key things:

(1) Adding CORS middleware to the request so we can call it from our react app, which will most likely be on a different domain than the server.

(2) Since we’ll be creating and processing promises within this function, the endpoint function must be async.

Next, here is the meeting creation logic.

try {
        const response = {}
        response.meetingResponse = await chime
            .createMeeting({
                ClientRequestToken: uuid(),
                MediaRegion: region,
            })
            .promise()

    res.send(response)
    } catch (err) {
        res.send(err)
}

This includes creating a response object which can be used to send information back to the client when the endpoint is reached.

The meeting response attribute on the response object is then assigned to the result of the create meeting API call.

The ClientRequestToken parameter is the meeting’s unique identifier (thus the usage of UUID); it can be any string as long as it uniquely identifies the meeting. The MediaRegion is the geographical area where the conference takes place.

At this point, you may either reply with a response or add an attendee straight away.

(2) Creating a meeting attendee

The following code will be used to create a meeting attendee:

response.attendee = await chime
        .createAttendee({
            MeetingId: response.meetingResponse.Meeting.MeetingId,
            ExternalUserId: uuid(),
        })
        .promise()

In the above code, you are performing another call on your chime object in the code above, which returns a promise with the meeting id and a user id sent in (ExternalUserId).

Anything goes for ExternalUserId as long as it uniquely identifies the user.

(3) Running the server

We should now get the following response when we launch our Express server and call the API endpoint:

{
   "meetingResponse":{
      "Meeting":{
         "MeetingId":"some unique meeting id",
         "ExternalMeetingId":null,
         "MediaPlacement":{
            "AudioHostUrl":"some AudioHostUrl",
            "AudioFallbackUrl"some AudioFallbackUrl",
            "ScreenDataUrl":"some ScreenDataUrl",
            "ScreenSharingUrl":"some ScreenSharingUrl",
            "ScreenViewingUrl":"some ScreenViewingUrl",
            "SignalingUrl":"some SignalingUrl",
            "TurnControlUrl":"some TurnControlUrl"
         },
         "MediaRegion":"us-east-1"
      }
   },
   "attendee":{
      "Attendee":{
         "ExternalUserId":"uuid for meeting",
         "AttendeeId":"uuid  for user",
         "JoinToken":"a unique token to join the meeting"
      }
   }
}

The server’s complete code is available at https://github.com/richlloydmiles/chime-video-calling-server-poc.

(4) Creating the client

The client-side does appear to be a little more complicated than the server-side, but if you eliminate a lot of the boilerplate code, it’s not that terrible.

Here is the link to two more additional modules that would be required for the client app.

https://www.npmjs.com/package/amazon-chime-sdk-js

https://www.npmjs.com/package/axios

Also, including Axios here makes it an easy way to make API calls.

Below written is the entire POC code:

import React, { useRef, useState } from 'react';
import './App.css';
import axios from 'axios'

import * as Chime from 'amazon-chime-sdk-js';

function App() {
  const [meetingResponse, setMeetingResponse] = useState()
  const [attendeeResponse, setAttendeeResponse] = useState()
  const [callCreated, setCallCreated] = useState(false)
  const videoElement = useRef()
  const startCall = async () => { 
    const response = await axios.get('http://localhost:5000/meeting')
    setMeetingResponse(response.data.meetingResponse)
    setAttendeeResponse(response.data.attendee)
    setCallCreated(true)
  }

  const joinVideoCall = async () => { 
    const logger = new Chime.ConsoleLogger('ChimeMeetingLogs', Chime.LogLevel.INFO);
    const deviceController = new Chime.DefaultDeviceController(logger);
    const configuration = new Chime.MeetingSessionConfiguration(meetingResponse, attendeeResponse);
    const meetingSession = new Chime.DefaultMeetingSession(configuration, logger, deviceController);

    const observer = {
      audioVideoDidStart: () => {
        meetingSession.audioVideo.startLocalVideoTile();
      },
      videoTileDidUpdate: tileState => {
        meetingSession.audioVideo.bindVideoElement(tileState.tileId, videoElement.current);
      }
    }

    meetingSession.audioVideo.addObserver(observer);
    const firstVideoDeviceId = (await meetingSession.audioVideo.listVideoInputDevices())[0].deviceId;
    await meetingSession.audioVideo.chooseVideoInputDevice(firstVideoDeviceId);
    meetingSession.audioVideo.start();
  }

  return (
    <div className="App">
      <header className="App-header">
        <video ref={videoElement}></video>
        <button disabled={!callCreated} onClick={joinVideoCall}> join call</button>
        <button onClick={startCall}>start call</button>
      </header>
    </div>
  );
}

export default App;

Here are certain state and ref properties established in our component after the first imports.

const [meetingResponse, setMeetingResponse] = useState()
  const [attendeeResponse, setAttendeeResponse] = useState()
  const [callCreated, setCallCreated] = useState(false)
  const videoElement = useRef()

The first two will simply be the different elements of our endpoint call’s response.

videoElement is an HTML reference that will hold the stream for your video, and callCreated is a flag that will be set after a successful meeting creation.

Next is the startCall function:

const startCall = async () => { 
    const response = await axios.get('http://localhost:5000/meeting')
    setMeetingResponse(response.data.meetingResponse)
    setAttendeeResponse(response.data.attendee)
    setCallCreated(true)
  }

The above code includes querying the API endpoint and setting our three state variables in this method.

Then there’s the function joinVideoCall (which is significantly more bulky than the other functions so let’s break it down).

const logger = new Chime.ConsoleLogger('ChimeMeetingLogs', Chime.LogLevel.INFO);

To begin, you would configure the logger to allow you to see logs and select the sort of log level you want to record. You may see the logs by opening your console in the browser while the app is running.

const deviceController = new Chime.DefaultDeviceController(logger);

The device controller is the module that manages the session’s linked devices.

const configuration = new Chime.MeetingSessionConfiguration(meetingResponse, attendeeResponse);

Here, the API response would be used for the configuration setup.

Finally, you end up building your meeting session, which would be the object you would utilize in the future.

const meetingSession = new Chime.DefaultMeetingSession(configuration, logger, deviceController);

The following link tells all about the videoTile:

https://aws.github.io/amazon-chime-sdk-js/interfaces/videotile.html

const observer = {
      audioVideoDidStart: () => {
        meetingSession.audioVideo.startLocalVideoTile();
      },
      videoTileDidUpdate: tileState => {
        meetingSession.audioVideo.bindVideoElement(tileState.tileId, videoElement.current);
      }
    }

    meetingSession.audioVideo.addObserver(observer);

Here is to add an observer that listens for two events:

  1. audioVideoDidStart – this event marks the beginning of your videoTile
  2. videoTileDidUpdate – for binding of videoTile to the video ref

Next, you have to:

  • Get a list of your currently connected devices
  • Choose the first choice from your list.
  • use it as the current video input.
  • Finally, the input stream is started.
const firstVideoDeviceId = (await meetingSession.audioVideo.listVideoInputDevices())[0].deviceId;
    await meetingSession.audioVideo.chooseVideoInputDevice(firstVideoDeviceId);
    meetingSession.audioVideo.start();

If you wish to add any level of complexity to this POC, you’ll need to update this.

Here is the jsx portion of a React project, which is quite simple:

<div className="App">
      <header className="App-header">
        <video ref={videoElement}></video>
        <button disabled={!callCreated} onClick={joinVideoCall}> join call</button>
        <button onClick={startCall}>start call</button>
      </header>
    </div>

You would be connecting your video ref to the video element here, then executing the functions you established before – either initiating or joining the video call.

callCreated is created to prevent you from joining a call that does not yet exist.

The entire source code for this is available at https://github.com/richlloydmiles/chime-video-calling-client-poc.

The server side of the POC was mostly taken from the AWS documentation on Github: https://github.com/aws/amazon-chime-sdk-js.

Summary

Building a video calling POC becomes easy if everything goes proper and sequential. Hence, the article perfectly describes each stage with related links. The above-mentioned steps and codes can be followed systematically to build a video-calling POC using ReactJS and AWS Chime.

Let's talk about your app

Hi, I am Sunil. Do you have any questions?
Feel free to get in touch