How I Created a Video Chat App using 100ms React SDK

mdmuneer

Muhammad Muneer

Posted on September 29, 2021

How I Created a Video Chat App using 100ms React SDK

JoinForm page

In this blog, I would like to burst out the popular belief, that building your own video chat app is too challenging.

Before we start building, let me explain briefly, what is 100ms ?

100ms is a cloud platform that allows developers to add video and audio conferencing to Web, Android and iOS applications.

The platform provides REST APIs, SDKs, and a dashboard that makes it simple to capture, distribute, record, and render live interactive audio, video.

Check out the 100ms Documentation here.

Basic Terminology to keep in mind
  • hmsStore - will hold the complete state of the application such as details of all the participants. We can also visualize this state at any time using the devtools extension.
  • hmsActions - will help us perform actions such as joining the room, mute our audio and send messages.
  • Room - When we join a conference call, the participants are said to be in a video call room.
  • Peer - A participant in the video call. You are the local peer while others are remote peers.
  • Track- Media. There are two types of track a peer can have - audio and video.

Now getting Started

Our Video Chat app will have the following features:

1. Video/Audio calling
2. Realtime Chat
Enter fullscreen mode Exit fullscreen mode
  • Create your react app

npx create-react-app my-app
cd my-app
npm start

  • Intsalling the dependencies

npm install --save @100mslive/hms-video@latest @100mslive/hms-video-react@latest

  • Now in the src/Index.js - we're rendering everything to the 'root' element and also wrapping the entire application with <HMSRoomProvider /> component (which will let us use the hooks for state and actions)

import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { HMSRoomProvider } from "@100mslive/hms-video-react";
import App from "./App";

const rootElement = document.getElementById("root");

ReactDOM.render(
  <HMSRoomProvider>
      <App />
  </HMSRoomProvider>,
  rootElement
);

Enter fullscreen mode Exit fullscreen mode
  • Now create a joinForm.js component, to join a room, and for that in the component, we need to call the join method on hmsActions. And add this component to the App.js.

we will have a form that includes two input fields accepting :

Username - The name of the user. This is the value that will be set on the peer object and be visible to everyone connected to the room.
authToken - A client-side token that is used to authenticate the user. You can read about how to generate this token here


import { useState } from "react";

import {
  useHMSActions,
  useHMSStore,
  selectIsLocalAudioEnabled,
  selectIsLocalVideoEnabled,
} from "@100mslive/hms-video-react";

const JoinForm = () => {
  const hmsActions = useHMSActions();
  const [inputValues, setInputValues] = useState({
    name: "",
    token: "",
  });

  const handleInputChange = (e) => {
    setInputValues((prevValues) => ({
      ...prevValues,
      [e.target.name]: e.target.value,
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    hmsActions.join({
      userName: inputValues.name,
      authToken: inputValues.token,
    });
  };

  return (
    <>
    <div className="showcase">
    <video autoPlay muted loop >
         <source src={backgroundVideo} type="video/mp4" />
    </video>
    <div className="overlay"></div>
    <form onSubmit={handleSubmit}>
      <h2 className="heading">Join Room</h2>
      <div className="input-container">
        <input
          className="inputfield"
          value={inputValues.name}
          onChange={handleInputChange}
          id="name"
          type="text"
          name="name"
          placeholder="Your name"
        />
      </div>
      <div className="input-container">
        <input
          className="inputfield"
          value={inputValues.token}
          onChange={handleInputChange}
          id="token"
          type="text"
          name="token"
          placeholder="Auth token"
        />
      </div>
      <button className="btn-primary">Join</button>
    </form>
    </div>
    </>
  );
};

export default JoinForm;

Enter fullscreen mode Exit fullscreen mode

Note - This code includes my css styled classes

  • let us now create Room.js component that will give us the list of peers and we'll render them.
import { useState, useEffect } from "react";
import { selectPeers, useHMSStore } from "@100mslive/hms-video-react";
import Peer from "./Peer"; 
import Chat from "./chat"; //will be imported
import ButtonBar from "./ButtonBar"; //will be imported

const Room = ({ toggleChat, setToggleChat }) => {
  const peers = useHMSStore(selectPeers);

  return (
    <div className="grid-container" style={{ backgroundColor: "#1B2156" }}>
      <div className="peers-container">
        {peers.map((peer) => (
          <Peer key={peer.id} peer={peer} />
        ))}
      </div>
      // {below} - will be added when creating the chat component
      <div className="chatbox-container">{toggleChat ? <Chat /> : null}</div>
    </div>
  );
};

export default Room;
Enter fullscreen mode Exit fullscreen mode

Also, create a Peer.js component, where we will be rendering video tiles in the peer-container.

import {
  selectVideoTrackByPeerID,
  useHMSActions,
  useHMSStore,
} from "@100mslive/hms-video-react";
import { useRef, useEffect } from "react";

const Peer = ({ peer }) => {
  const videoRef = useRef(null);
  const hmsActions = useHMSActions();
  const videoTrack = useHMSStore(selectVideoTrackByPeerID(peer.id));

  useEffect(() => {
    if (videoRef.current && videoTrack) {
      if (videoTrack.enabled) {
        hmsActions.attachVideo(videoTrack.id, videoRef.current);
      } else {
        hmsActions.detachVideo(videoTrack.id, videoRef.current);
      }
    }
  }, [videoTrack, hmsActions]);

  return (
    <div className="peer-container">
      <video
        ref={videoRef}
        className={`peer-video ${peer.isLocal ? "local" : ""}`}
        autoPlay
        muted
      />
      <div className="peer-name">
        {peer.name} {peer.isLocal ? "(You)" : ""}
      </div>
    </div>
  );
};

export default Peer;
Enter fullscreen mode Exit fullscreen mode
  • In the App.js component, we will also be adding leaving room functionality, which we can do by setting up leave method on hmsActions.

import JoinForm from "./components/JoinForm";
import "./styles.css";
import Room from "./components/Room";
import Notification from "./components/Notifications";
import { useEffect } from "react";
import {
  selectIsConnectedToRoom,
  useHMSActions,
  useHMSStore,
} from "@100mslive/hms-video-react";
import ButtonBar from "./components/ButtonBar";

const App = () => {
  const isConnected = useHMSStore(selectIsConnectedToRoom);
  const hmsActions = useHMSActions();

  useEffect(() => {
    window.onunload = () => {
      if (isConnected) {
        hmsActions.leave();
      }
    };
  }, [hmsActions, isConnected]);

  return (
    <div className="App" >
      {isConnected ? (
        <>
          //these components will be added later
          <Notification />
          <Room />
          <ButtonBar />
        </>
      ) : (
        <JoinForm />
      )}
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode
  • Now it's time to add the buttons bar component, which will include Mute/Unmute button, leave room button and Chatbox button.

import React, { useState, useEffect, useMemo } from "react";
import { BiMicrophone } from "react-icons/bi";
import { BiMicrophoneOff } from "react-icons/bi";
import { BiVideo } from "react-icons/bi";
import { BiVideoOff } from "react-icons/bi";
import { AiOutlineSetting } from "react-icons/ai";
import { FaPhone } from "react-icons/fa";
import { MdScreenShare } from "react-icons/md";
import { MdStopScreenShare } from "react-icons/md";

import {
  useHMSActions,
  useHMSStore,
  selectPeers,
  selectLocalPeer,
  selectIsLocalAudioEnabled,
  selectIsConnectedToRoom,
  selectIsLocalVideoEnabled,
} from "@100mslive/hms-video-react";
import Chat from "./chat";

const ButtonBar = ({ peer }) => {
  const peers = useHMSStore(selectPeers);
  const localPeer = useHMSStore(selectLocalPeer);
  const videoEnabled = useHMSStore(selectIsLocalVideoEnabled);
  const audioEnabled = useHMSStore(selectIsLocalAudioEnabled);
  const hmsActions = useHMSActions();
  const isConnected = useHMSStore(selectIsConnectedToRoom);
  const [toggleChat, setToggleChat] = useState(false);


  const toggleAudio = () => {
    hmsActions.setLocalAudioEnabled(!audioEnabled);
  };

  const toggleVideo = () => {
    hmsActions.setLocalVideoEnabled(!videoEnabled);
  };

  return (
    <>
      <div className="control-bar">
        <button
          className="chatbox-btn"
          onClick={() => setToggleChat(!toggleChat)}
        >
          In-call Messages
        </button>
        {toggleChat ? <Chat /> : null}
        <div className="center-buttons">
          <nav>
            <button className="btn-control" onClick= {toggleAudio}>
              {audioEnabled ? (
                <BiMicrophone size="1.8em" />
              ) : (
                <BiMicrophoneOff size="1.8em" />
              )}
            </button>
            <button
              className="btn-control"
              alt="Leave Room"
              onClick={toggleVideo}
            >
              {videoEnabled ? (
                <BiVideo size="1.8em" />
              ) : (
                <BiVideoOff size="1.8em" />
              )}
            </button>
          </nav>
          {isConnected && (
            <FaPhone
              size="2em"
              id="leave-btn"
              className="btn-danger"
              onClick={() => hmsActions.leave()}
            />
          )}
        </div>
        <div className="App-name">VidChat</div>
      </div>
    </>
  );
};

export default ButtonBar;

Enter fullscreen mode Exit fullscreen mode

Note - That we have to create a chat component which will be included in the button bar. Also, I have added React icons for styling.

  • Creating a chat.js component for In-call messaging, using await hmsActions.sendBroadcastMessage('message') which will send a message to every peer in the room and use useHMSStore (selectHMSMessages).

import React from "react";
import { useState, useEffect } from "react";
import {
  useHMSActions,
  useHMSStore,
  selectLocalPeer,
  selectPeers,
  selectHMSMessages,
  selectMessagesByRole,
  selectMessagesByPeerID,
  selectBroadcastMessages,

} from "@100mslive/hms-video-react";
import "dayjs/locale/es";
import * as dayjs from "dayjs";

const relativeTime = require("dayjs/plugin/relativeTime");
const localizedFormat = require("dayjs/plugin/localizedFormat");
dayjs.extend(relativeTime);
dayjs.extend(localizedFormat);

const Chat = () => {
  const hmsActions = useHMSActions();
  const getMessages = useHMSStore(selectHMSMessages);
  const [inputMessage, setInputMessage] = useState("");

  const typedMessages = async () => {
    await hmsActions.sendBroadcastMessage(inputMessage);
    setInputMessage("");
  };

  const handleOnKeyDownEvent = (e) => {
    if (e.keyCode === 13) {
      typedMessages(inputMessage);
      setInputMessage("");
    }
  };
  function renderMessages(data) {
    console.log(data);
    return (
      <>
        <div>
          <div className="message-sender" key={data.id}>
            {data.senderName}
            <div className="date">{dayjs(data.time).format("LT")}</div>
          </div>
          <div className="message ">{data.message} </div>
        </div>
      </>
    );
  }



  return (
    <>
      <div className="chat">
        <div className="chat-title">
          <h2> Inbox </h2>
        </div>

        <div className="messages">
          <div className="messages-content scrollbar ">
            {getMessages.map(renderMessages)}
          </div>
        </div>

        <div className="message-box">
          <textarea
            type="text"
            className="message-input"
            placeholder="Type a message . . ."
            value={inputMessage}
            onChange={(e) => setInputMessage(e.target.value)}
          ></textarea>
          <button className="message-submit" type="button" onClick={typedMessages}>
            Send
          </button>
        </div>
      </div>
    </>
  );
};

export default Chat;

Enter fullscreen mode Exit fullscreen mode

Add this component to the button bar.

Note - I have used Day.js for getting formatted time in the chatbox

Video calling view -

Main screen


Some other features that can be added are -

  • Notifications
  • Screen-Sharing
  • Show Audio levels

Hope this article is useful. Feel free to reach out to me for any queries.

Full Code on Github -

GitHub logo MdMuneer / VidChat

Video Chatting Web App using 100ms

Video Chat Web App using 100ms with intuitive user-interface.

Video Chat Features -

  • In-call Messaging

  • Mute/Unmute (Audio/video)

  • Live Notifications

đź’– đź’Ş đź™… đźš©
mdmuneer
Muhammad Muneer

Posted on September 29, 2021

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

Sign up to receive the latest update from our blog.

Related