DEV Community

Cover image for Zoom Clone App with React and 100ms SDK (part II)
Benjamin Busari
Benjamin Busari

Posted on

Zoom Clone App with React and 100ms SDK (part II)

This article is a continuation of the How to build a Zoom Clone App with React tutorial.

Part 1 focuses on creating the Zoom layout. That is a required step before you can follow the tutorial in this article, so kindly check that first before continuing with this.

Zoom is a cloud-based video conferencing platform that can be used for video conferencing meetings, audio conferencing, webinars, meeting recordings, and live chat. You need a free account to start your own calls for up to 100 people; paid versions can support up to 1,000 people. You can make unlimited phone calls, hold unlimited meetings, and even record both.

Overview of 100ms?

100ms is a cloud-based platform that lets you integrate video and audio conferencing into your application. It provides APIs and SDKs through which you can set up and manage telecommunication services on the client and server-side applications.

It provides a dashboard that makes it quite easy to manipulate descriptive features of a video/audio platform from in-app interaction to the session recording. The 100ms SDK and packages are easy-to-use tools that save time without compromising effectiveness.

Prerequisites and What You Will Learn

As a prerequisite to understanding and following this tutorial, you should have basic knowledge of React.

In this article, you will learn how to build this clone using the following concepts:

  • Creating a new 100ms Video Conference Application
  • Connect your client React app to 100ms to get the APP_ACCESS_KEY and APP_SECRET from the developer section in the dashboard.
  • Customizing the SDK’s components by overwriting CSS classes and using Context from the SDK

Important Terms to Know

Before diving into the tutorial, here are a few terms you should be familiar with:

  • Peer: A peer is an object returned by 100ms SDKs that contains all information about a user - name, role, video track, etc.
  • Room: A room is a basic object that 100ms SDKs return on a successful connection. This contains references to peers, tracks, and everything you need to render a live audio/video app.
  • Role: A role defines who a peer can see/hear, the quality at which they publish their video, whether they have permission to publish video/screen share, mute someone, change someone's role, or more in a call.
  • Track: A track represents either the audio or video that a peer is publishing.
  • PeerCount: A peer count indicates the number of users participating in the room.

This tutorial will cover the following:

  • Muting and unmuting the audio and video for both local and remote peers.
  • Sharing screen for presentation for the host.
  • Creating a modal to invite other users to the video conference

Let’s Build Our Zoom Clone App

Developers Setup

For this tutorial, you will install packages with NPM, so you need to have Node installed on your system. Alternatively, you can use yarn if you prefer that.

For this tutorial, I used Node v16.15.0. I encourage you to use the same version if you are coding along.

Setting up your 100ms Application

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, and video.

To build a live app on 100ms.live, you need a 100ms.live account. If you do not have one, head over to the login to login to the dashboard. You can do this either by login in with your Google account, GitHub or your email.

To create a live app:

  1. Go to your 100ms dashboard
  2. Select either to continue with Google, Github, or your Email
  3. Choose a Template (Video Conferencing)

100ms Template

  1. Select Account Type (Personal)

Account Type

  1. Set your domain and region

domain selection

  1. Select option to either join as a host or as a guest (for this tutorial, select HOST)

role selection

In this tutorial, the guest role doesn't have the privilege to share their screens, you can give yours the opportunity to share screen while setting your roles.

Congratulations your App is Live 👊. Next is to access the developer's panel so we can save our token, access keys, roomID etc.

Project Setup

  • Link 100ms SDK to the app: Install the 100ms react SDK and project dependencies.
## npm
npm install --save @100mslive/react-sdk@latest
## yarn
yarn add @100mslive/react-sdk@latest
Enter fullscreen mode Exit fullscreen mode

Retrieving Credentials

  • Retrieve Credentials: Get token_endpoint, room_id, APP_ACCESS_KEY, and APP_SECRET from the developer section of the dashboard.

Once you’re done creating your custom application(in this case we created a video conference app), head on over to the Developers tab to get your token endpoint URL. This endpoint URL is very important as this is where we get our token from.

The next thing to do is for us to get the ROOM ID , To obtain the room ID, head to the Rooms tab on the Dashboard. You’ll see an existing room. Copy the room ID, we will be using this soon.

roomID

Next, navigate to the developers tab and copy your endpointURL and room ID

endpoint

You can also set roles (guest or host) function like screen share, video and audio mute

roles function

  • Create Room: create a video room.
  • Build Clone: Use hmsAction and hmsStore hooks to start building our clone. Fill in token_endpoint and room_id.

At the end of this tutorial, your ZoomMeet clone Interface should look like this:

landing page

Building the clone App

To use the 100ms SDK that we installed previously, we need to be familiar with two hooks:

  1. hmsActions: This will help us perform actions such as joining the room, muting our audio/video, and sending messages.
  2. hmsStore: This contains the complete state of the room at any given time such as the participant details etc.

After installing the SDK, navigate to your index.js file and have it look like this:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { HMSRoomProvider } from "@100mslive/react-sdk";

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <HMSRoomProvider>
    <App />
  </HMSRoomProvider>,
);
Enter fullscreen mode Exit fullscreen mode

Next, create a file and name it fetchToken.js

Now, let's set some variables in our fetchtoken.js file. the code below will fetch your token from the saved endpoint URL returning a response, this is what we then further use in our code to set our Room for the conference.

const endPoint = "https://prod-in2.100ms.live/hmsapi/zoomclone.app.100ms.live/";
//your saved endpoint URL from your dashboard

const fetchToken = async (user_id) => {
  const response = await fetch(`${endPoint}api/token`, {
    method: "POST",
    body: JSON.stringify({
      user_id,  // user_id different from the generated id in your developers tab
      role: "host", //host, guest
      room_id: "6288c1d9b873787aa26f06f0" //your room id
    })
  });
  const { token } = await response.json();
  return token;
};
export default fetchToken;
Enter fullscreen mode Exit fullscreen mode
  • Fill in your token_endpoint and room_id, as is shown above.
  • To call the join function, add the following code to your JoinRoomfunction in the JoinForm*.js* file. This is necessary because when a user joins a room, we want to display the room the user joined.
  • You can get the UI layout and stylings here.
import React, { useState } from 'react';
import './style.css';
import NavBar from '../UserInterface/navBar';

import { useHMSActions} from '@100mslive/react-sdk';
import fetchToken from "../fetchToken";

const JoinRoom = () => {
    const hmsActions = useHMSActions();
    const [userName, setUserName] = useState("");

    const handleSubmit = async (userName) => {
        const token = await fetchToken(userName);
        hmsActions.join({ 
        userName,
        authToken: token
        });
    };
    return(
        <>
            <NavBar />
            <hr></hr>
            <div id="content_container" style={{minHeight: 872}}  className="zoom-newcontent ">
                <div id="content" className="main-content">
                    <div className="mini-layout" id="join-conf">
                        <div className="mini-layout-body">
                            <h1 style={{fontSize: 25}}>Join Meeting</h1>
                            <div className="box">
                                <form id="join-form" className="form-vertical" onSubmit={(e) => {
                                        e.preventDefault();
                                        handleSubmit(userName);
                                    }}>
                                    <div className="form-group confno" style={{marginBottom: 30}}>
                                        <div className="controls">
                                            <label htmlFor="join-confno" style={{color: 747486, fontSize: 15,marginBottom: 10}}>Meeting ID or Personal Link Name</label>
                                            <input aria-describedby="rule-tip" id="join-confno" 
                                                type="text" 
                                                className="form-control input-lg confno" 
                                                autoComplete="off" maxLength="100" 
                                                placeholder="Enter Meeting ID or Personal Link Name" 
                                                value={userName}
                                                onChange={(e) => setUserName(e.target.value)}
                                                name="userName"
                                                required
                                                />
                                            <div id="errorContainer" className="wc-new-syle">
                                                <div id="join-errormsg" className="error hideme"><i></i><span></span></div>
                                            </div>
                                        </div>
                                    </div>
                                    <div className="form-group" style={{marginBottom: 16}}>
                                        <div className="controls">
                                            By clicking "Join", you agree to our <a href="https://zoom.us/terms">Terms of Services</a> and <a href="https://zoom.us/privacy">Privacy Statement</a>
                                        </div>
                                    </div>
                                    <div className="form-group" style={{marginBottom: 72}}>
                                        <div className="controls wc-new-syle">
                                            <button id="btnSubmit" role="button" style={{ width: 200, padding: 5}} className="btn btn-primary user submit">Join</button>
                                        </div>
                                    </div>
                                    <div className="form-group">
                                        <div className="controls wc-new-syle">
                                            <a id="btnRoomSystemJoin" className="doc" href="https://zoom.us/meeting/rooms">Join a meeting from an H.323/SIP room system</a>
                                        </div>
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </>

    )
}
export default JoinRoom;
Enter fullscreen mode Exit fullscreen mode

From the above, when the button join is clicked, the handlesubmit function is triggered which fetches the token to authenticate the user trying to join the room.

Join Room

Next, we create a file and name it VideoTile.js, the function of the VideoTile.js is to display the video.

In the Video.js, your code should look like this:

import React from "react";
import {
  useHMSActions,
  useHMSStore,
  selectCameraStreamByPeerID
} from "@100mslive/react-sdk";

const VideoTile = ({ peer, isLocal }) => {
  const hmsActions = useHMSActions();
  const videoRef = React.useRef(null);
  const videoTrack = useHMSStore(selectCameraStreamByPeerID(peer.id));

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

  return (
    <div className="flex m-auto">
      <div className="relative">
        <video
          ref={videoRef}
          autoPlay={true}
          playsInline
          muted={true}
          className={`object-cover h-64 w-screen h-screen rounded-lg shadow-lg ${
            isLocal ? "mirror" : ""
          }`}
        ></video>
        <div className="top-0 w-full absolute flex justify-center">
          <div className="px-2 py-1 text-sm bg-gray-600 text-white mt-2 ml-2 rounded-lg">{`${peer.name}`}</div>
        </div>
      </div>
    </div>
  );
};

export default VideoTile;
Enter fullscreen mode Exit fullscreen mode

Next, create another file and name it Peer.Js.

import { useVideo } from "@100mslive/react-sdk";

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

export default Peer;
Enter fullscreen mode Exit fullscreen mode

The Peer.js file sets

Next, navigate to the ControlBar.js, this will allow us to control our room, adding toggle bars, etc to the room. Having designed the ControlBar.js in the previous tutorial, now we proceed to adding functionalities to each clickable buttons.

import React, {useState} from "react";
import {
  useHMSActions,
  useHMSStore,
  selectIsLocalAudioEnabled,
  selectIsLocalVideoEnabled,
  selectIsLocalScreenShared,
  selectPeerCount
} from "@100mslive/react-sdk";

const ControlBar = () => {
  const hmsActions = useHMSActions();
  const isLocalAudioEnabled = useHMSStore(selectIsLocalAudioEnabled);
  const isLocalVideoEnabled = useHMSStore(selectIsLocalVideoEnabled);
  const isLocalScreenShared = useHMSStore(selectIsLocalScreenShared);
  const countParticipants = useHMSStore(selectPeerCount);

  const toggleAudio = async () => {
    await hmsActions.setLocalAudioEnabled(!isLocalAudioEnabled);
  };
  const toggleVideo = async () => {
    await hmsActions.setLocalVideoEnabled(!isLocalVideoEnabled);
  };
  const toggleScreen = async () => {
    await hmsActions.setScreenShareEnabled(!isLocalScreenShared);
  }
  const participants =  () => {
    hmsActions.setParticipantsCount(!countParticipants);
  }
  const invite = () => {
    document.getElementById("invite").classList.toggle("show");
  }
  const [show, setShow] = useState(false);

  return (

    <div class="main__controls">
        <div className="main__controls__block">
            <div className="main__controls__button main__mute_button" onClick={toggleAudio}>
                {isLocalAudioEnabled ? (
                  <>
                    <i className="fas fa-microphone"></i>
                    <span>Mute</span>
                  </>
                  ) : (
                  <>
                    <i className="fas fa-microphone-slash"></i>
                    <span>UnMute</span>
                  </>
                  )}
            </div>
            <div onClick={toggleVideo} className="main__controls__button main__video_button" >
                {isLocalVideoEnabled ? (
                  <>
                    <i className="fas fa-video"></i>
                    <span>Stop Video</span>
                  </>
                ) : (
                  <>
                    <i className="fas fa-video"></i>
                    <span>Play Video</span>
                  </>
                )}
            </div>
            <div className="main__controls__button">
                <i className="fas fa-shield-alt"></i>
                <span>Security</span>
            </div>
            <div className="main__controls__button part" onClick={invite}>
              <i className="fas fa-user-friends"></i>
              <span className="partLink">Participants <span className="participants">{countParticipants}</span></span>
                <div id="invite" className="dropdown-content">
                  <button onClick={() => setShow(true)}>Invite  
                    <span className="share-icon">

                    </span>
                  </button>
                </div>
                <Modal onClose={() => setShow(false)} show={show} />
            </div>
            <div className="main__controls__button">
                <i className="fas fa-comment-alt"></i>
                <span>Chat</span>
            </div>
            <div onClick={toggleScreen} className="main__controls__button main__video_button" >
                <i className="fas fa-desktop"></i>
                {isLocalScreenShared ? "Unshare" : "Share Screen"}
            </div>
            <div className="main__controls__button">
                <i className="fas fa-record-vinyl"></i>
                <span>Record </span>
            </div>
            <div className="main__controls__button">
                <i className="fas fa-laugh"></i>
                <span>Reactions</span>
            </div>
            <div className="main__controls__button">
                <i className="fas fa-retweet"></i>
                <span>Apps</span>
            </div>
            <div className="main__controls__button">
                <i className="fas fa-clipboard"></i>
                <span>Whiteboard</span>
            </div>
        </div>
        <div className="main__controls__block">
          <div onClick={() => { hmsActions.endRoom(false, "reason") && hmsActions.leave();
            }} 
            className="main__controls__button"
          >
            <span className="leave_meeting">Leave Meeting</span>
          </div>
        </div>
    </div>
  );
};

export default ControlBar;
Enter fullscreen mode Exit fullscreen mode

With the ControlBar, we can know how many participants we have in the room, we can share the screen during a presentation by clicking on Share Screen, and you can also invite others into the room by clicking on the Participants button and then clicking on Invite.

room

Now, let us create a modal to display when we click on the invite. The modal will show the room name and meeting passcode which also allows the user to copy the invite link and share.

Create a folder and name it Modal, then inside the folder create a file and name it Modal.js file, your modal.js file should look like this:

import React from 'react';
import './modal.css';

const Modal = props => {

    if(!props.show) {
        return null
    }
    const fetchToken = {
        role: "host", //host, guest
        room_id: "6288c1d9b873787aa26f06f0",
        room_name: "FRONTEND"
      };

    return(
        <div className="modal">
            <div className="modal-content">
                <div className="modal-header">
                    <button onClick={props.onClose} className="button">Close</button>
                    <h5 className="modal-title">Invite People to join meeting {fetchToken.room_name}</h5>
                </div>
                <div className="modal-body">
                    <input className="modal-input" placeholder="Choose from the list or type to filter" />
                </div>
                <div className="modal-footer">
                    <button className="button">Copy invite link</button>
                    <div className="btn-right">
                        <h5>Meeting Passcode: {fetchToken.room_id} </h5>
                        <button className="button">Invite</button>
                    </div>
                </div>
            </div>
        </div>
    )
}
export default Modal;
Enter fullscreen mode Exit fullscreen mode

Now, let's style the modal.js file. create a file and name it modal.css.

.modal {
    background-color: #1C1E20;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 500px;
    width: 50%;
    margin-top: 40px;
    margin-left: 200px;
}
.modal-content {
    width: 600px;
    height: 410px;
    background-color: #1C1E20;
}
.modal-header {
    display: flex;
    gap: 1em;
    text-align: center;
}
.button {
    padding: 5px;
    background-color: #1C1E20;
    border: 0;
}
.modal-input {
    width: 100%;
    padding: 5px;
    padding: 2px;
}
.modal-header, .modal-footer {
    padding: 10px;
}
.modal-title {
    margin: auto;
}
.modal-body {
    padding: 10px;  
}
.modal-footer {
    margin-top: 250px;
    display: flex;
}
.btn-right {
    display: flex;
    justify-content: space-between;
    margin: auto;
    gap: 3em;
}
Enter fullscreen mode Exit fullscreen mode

modal

Now, we navigate to the Room.js file, our localPeer will be known as class. Your Room.js file should be modified to look like this:

import React from "react";
import VideoTile from "./VideoTile";
import './view.css';
import {
  useHMSStore,
  selectLocalPeer,
  selectPeers
} from "@100mslive/react-sdk";
import ControlBar from "./Control/ControlBar";

const Room = () => {

  const localPeer = useHMSStore(selectLocalPeer);
  const peers = useHMSStore(selectPeers);

  return (
    <div class="main"> 
      <div class="main__left">
        <div class="main__videos">
          <div id="video-grid">
            <div className="flex flex-col mt-20">
              <div className="flex bg-gray-900 w-screen min-h-screen p-2 flex-wrap">
                {localPeer && <VideoTile peer={localPeer} isLocal={true} />}
                {peers &&
                  peers
                    .filter((peer) => !peer.isLocal)
                    .map((peer) => {
                      return (
                        <>
                          <VideoTile isLocal={false} peer={peer} />
                        </>
                      );
                    })}
              </div>
            </div> 
          </div>
        </div>
        <ControlBar />
      </div>
      <div className="main__right">
          <div className="main__header">
              <h6>Chat</h6>
          </div>
          <div className="main__chat_window">
              <ul className="messages">

              </ul>
          </div>
          <div className="main__message_container">
              <input id="chat_message" type="text" placeholder="Type message here..." />
          </div>
      </div>
    </div>
  );
};

export default Room;
Enter fullscreen mode Exit fullscreen mode

Finally, we can modify our App.js file to look like this:

import './components/styling/style.css'
import {
  useHMSActions,
  useHMSStore,
  selectIsConnectedToRoom
} from "@100mslive/react-sdk";
import Room from "./components/Room";
import JoinRoom from './components/LandingPage/JoinForm';
import fetchToken from "./components/fetchToken";

const App = () => {
    const hmsActions = useHMSActions();
    const isConnected = useHMSStore(selectIsConnectedToRoom);
    const handleSubmit = async (userName) => {
    const token = await fetchToken(userName);
    hmsActions.join({ authToken: token, userName });
  };

  return (
    <>{isConnected ? <Room /> : <JoinRoom handleSubmit={handleSubmit} />}</>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Your application should look like this:

final

Conclusion

In this tutorial, you have successfully created a zoom clone, and added functionalities to the share button, participants, audio, and invite. What the Zoom App lacks now is reactions (emojis and stickers), chat, and how to copy, share links and add another user to the video conference. I'll walk you through implementing this in Part 3.

100ms is a cloud-based platform that lets you integrate video and audio conferencing into your application. It provides APIs and SDKs through which you can set up and manage telecommunication services on the client and server-side applications.

The increasingly virtual, remote-first world, needs an infrastructure provider that allows these experiences to happen seamlessly. 100ms' best-in-class quality and ease of use are fuelling this revolution. 100ms provides multiple solutions matching both common and unique use cases, with just a few clicks and hooks. 💥.

You can also check out the code on GitHub here

You might also be interested in other guides using React and 100ms SDK:

Building a Google Classroom Clone with React and 100ms SDK

Building a Twitch Clone with React

Building a Discord Stage Channel

Top comments (0)