DEV Community

Cover image for πŸ”₯ Building a video chat app with screen-sharing and React hooks 🀯
Sagar Kava for Video SDK

Posted on

πŸ”₯ Building a video chat app with screen-sharing and React hooks 🀯

What is this article about?

You wrote this article to delve into the intricacies of building a video conferencing app with screen-sharing using React. Your goal was to highlight the complexity and transformative potential of this technology.

Why did I create this article?

I wrote this article to discuss creating a complex video conferencing app with screen-sharing capabilities using React. I explored how technology, code, and user interfaces combine to go beyond regular communication. Additionally, I discussed how such apps transform human interaction in the modern tech era.

Video SDK - Live video infrastructure for every developer

Video SDK is a robust live video platform with features like real-time video conferencing, chat, screen sharing, and more. It offers SDKs for various platforms, empowering developers to create customizable video experiences, with support for popular programming languages like JavaScript, React, React Native, Flutter, Android Native, iOS Native, and more. Plus, it uses a pay-as-you-go pricing model and is suitable for businesses of all sizes. https://www.videosdk.live/

Live video infrastructure<br>
for every developer<br>

What do we offer for you?

The Video SDK RTC React JS SDK offers an array of impressive features, each designed to enhance your virtual communication and collaboration experience:

  • Free Usage: You get 10,000 minutes free each month.
  • Effortless Setup: It's low-code and serverless, making it easy to use.
  • Full Multimedia: Enjoy real-time audio, video, and data streams.
  • High-Quality Screen Sharing: Share your screen in HD and Full HD.
  • Customizable UI: Personalize the interface as needed.
  • Rich Chat: Communicate with rich media in the chat.
  • Social Media Integration: Connect with platforms like Facebook and Youtube (RTMP out support).
  • Intelligent Speaker Switch: Automatic speaker management.
  • Cloud Recording: Record meetings in the cloud.

If you get stuck at any point or need help in understanding a concept, you can put your query in our Discord Channel.

Building a video chat app with screen-sharing and React

Prerequisites

  • First up, you'll need a Video SDK Account Don't have one yet? No sweat! Just tag along with our Video SDK Dashboard guide.
  • Next, make sure you've got a grasp of React's fundamentals. We're talking React 101 here, folks!
  • The star of the show, of course, is our React Video SDK Package manager. You'll want that in your arsenal.
  • Don't forget to have Node and NPM cozying up on your device. They're your trusty sidekicks for this journey.
  • Now, let's talk hooks - get comfy with useState, useRef, and useEffect. They're your secret weapons.
  • If you're feeling ambitious, you can dive into the React Context API. It's optional, but it can add some extra oomph to your app.

Let's Get started

You can use react-scripts to generate project template or any other react boilerplate.

Create new project using create-react-app

Enter fullscreen mode Exit fullscreen mode

Installing the dependencies

##npm

$ npm install "@videosdk.live/react-sdk"

//For the Participants Video
$ npm install "react-player"

or 

##yarn

$ yarn add "@videosdk.live/react-sdk"

//For the Participants Video
$ yarn add "react-player"
Enter fullscreen mode Exit fullscreen mode

Structure of the project

.
β”œβ”€β”€ node_modules
β”œβ”€β”€ public
β”œβ”€β”€ .env
β”œβ”€β”€ src
β”‚ └── api.js
β”‚ └── App.js
β”‚ └── index.css
β”‚ └── index.jsx
β”œβ”€β”€ package.json
...
.
Enter fullscreen mode Exit fullscreen mode

Absolutely, we're going to harness the power of functional components to tap into React's fantastic reusable component setup. Here's what's on our menu:

  • User Components: These will handle user-related functionalities.
  • Video Components: These will take care of everything related to videos.
  • Control Components: These are the real MVPs, covering mic, camera, and the all-important exit function, ensuring smooth sailing during your video experience.

Let's get started and make some magic happen!

App Architecture:

Our app is going to be structured like this:

  • MeetingView Component: This is the main hub of our app. Inside it, you'll find the ParticipantView. This view is where we'll display essential participant information such as their name, video stream, audio status, and more.
  • Controls Component: Think of this as the command center. It's where users take control of the app. Here, they can perform actions like leaving the meeting and toggling their media settings.

1

We're breaking this down into two key files:

API.js: This file handles API tasks, like creating unique meeting IDs and tokens.

App.js: This is where the magic happens. It renders the MeetingView and gets you into the meeting. It's your video adventure's front door.

With these two files in play, we're all set to roll!

Let's Dive into API.js

Before we take the next leap, our first task is to craft an API request in API.js. This request is your golden ticket to generating a one-of-a-kind meeting ID. But hold on, there's a catch - you'll need an authentication token.

You can snag this token in two ways: either whip it up using the videosdk-rtc-api-server-examples or go directly to the "<Generated-from-dashbaord>" for developers.

2

src/API.js

//Auth token we will use to generate a meeting and connect to it
export const authToken = "<Generated-from-dashbaord>";
// API call to create meeting
export const createMeeting = async ({ token }) => {
  const res = await fetch(`https://api.videosdk.live/v2/rooms`, {
    method: "POST",
    headers: {
      authorization: `${authToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({}),
  });
  //Destructuring the roomId from the response
  const { roomId } = await res.json();
  return roomId;
};
Enter fullscreen mode Exit fullscreen mode

Nots πŸ“
One should have a VideoSDK account to generate token. Visit VideoSDK dashboard to generate token.

Building App.js Wireframe

In the App.js wireframe, we'll harness Video SDK Hooks and Context Providers. Here's the quick breakdown:

  1. MeetingProvider: A Context Provider that shares data with multiple components at various nesting levels.
  2. MeetingConsumer: The Context Consumer that listens for changes in the Provider's data.
  3. u*seMeeting*: A hook that handles all things related to the meeting, like joining, leaving, and adjusting mic and webcam settings.
  4. useParticipant: This hook focuses on individual participants, managing their name, webcam stream, mic stream, and more. The Meeting Context keeps tabs on all the changes in the meeting. Let's dive in and tweak App.js to make it happen! πŸš€

src/App.js


import "./App.css";
import React, { useEffect, useMemo, useRef, useState } from "react";
import {
  MeetingProvider,
  MeetingConsumer,
  useMeeting,
  useParticipant,
} from "@videosdk.live/react-sdk";
import { authToken, createMeeting } from "./API";
import ReactPlayer from "react-player";

function JoinScreen({ getMeetingAndToken }) {
  return null;
}

function ParticipantView(props) {
  return null;
}

function Controls(props) {
  return null;
}

function MeetingView(props) {
  return null;
}

function App() {
  const [meetingId, setMeetingId] = useState(null);

  const getMeetingAndToken = async (id) => {
    const meetingId =
      id == null ? await createMeeting({ token: authToken }) : id;
    setMeetingId(meetingId);
  };

  const onMeetingLeave = () => {
    setMeetingId(null);
  };

  return authToken && meetingId ? (
    <MeetingProvider
      config={{
        meetingId,
        micEnabled: true,
        webcamEnabled: true,
        name: "C.V. Raman",
      }}
      token={authToken}
    >
      <MeetingConsumer>
        {() => (
          <MeetingView meetingId={meetingId} onMeetingLeave={onMeetingLeave} />
        )}
      </MeetingConsumer>
    </MeetingProvider>
  ) : (
    <JoinScreen getMeetingAndToken={getMeetingAndToken} />
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Let's get cracking on implementing the Join Screen!

In the Join Screen, we're keeping things straightforward with two essential features:

Create New Meeting: This option lets users kickstart a fresh meeting from scratch.

Join the Meeting: Here, users can hop into an existing meeting using a Meeting ID.

Our star of the show here is the JoinScreen Component. It's where the action happens. Time to bring this screen to life! πŸš€

src/App.js

function JoinScreen({ getMeetingAndToken }) {
  const [meetingId, setMeetingId] = useState(null);
  const onClick = async () => {
    await getMeetingAndToken(meetingId);
  };
  return (
    <div>
      <input
        type="text"
        placeholder="Enter Meeting Id"
        onChange={(e) => {
          setMeetingId(e.target.value);
        }}
      />
      <button onClick={onClick}>Join</button>
      {" or "}
      <button onClick={onClick}>Create Meeting</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Implement Participant View and Controls

Let's roll up our sleeves and get to work on implementing the ParticipantView and Controls. These components are vital for managing participant interactions effectively.

ParticipantView: This component will display the view for individual participants, showing their video, audio, and other relevant information.

Controls: The control panel that allows you to perform actions like muting/unmuting the participant's microphone and toggling their camera.

With these components in place, you'll have full control over your participants' experience. Time to dive in! πŸš€

  • Forwarding Ref for Mic and Camera: We'll use React's useRef to reference audio and video components for participant control.
  • useParticipant Hook: This hook manages properties and events for a specific participant using their ID.
  • MediaStream API: We use MediaStream to handle audio and video playback.

src/App.js

function ParticipantView(props) {
  const micRef = useRef(null);
  const { webcamStream, micStream, webcamOn, micOn, isLocal, displayName } =
    useParticipant(props.participantId);

  const videoStream = useMemo(() => {
    if (webcamOn && webcamStream) {
      const mediaStream = new MediaStream();
      mediaStream.addTrack(webcamStream.track);
      return mediaStream;
    }
  }, [webcamStream, webcamOn]);

  // webcamStream.pause()
  // micStream.pause()

  // webcamStream.resume()
  // micStream.resume()

  useEffect(() => {
    if (micRef.current) {
      if (micOn && micStream) {
        const mediaStream = new MediaStream();
        mediaStream.addTrack(micStream.track);

        micRef.current.srcObject = mediaStream;
        micRef.current
          .play()
          .catch((error) =>
            console.error("videoElem.current.play() failed", error)
          );
      } else {
        micRef.current.srcObject = null;
      }
    }
  }, [micStream, micOn]);

  return (
    <div key={props.participantId}>
      <p>
        Participant: {displayName} | Webcam: {webcamOn ? "ON" : "OFF"} | Mic:{" "}
        {micOn ? "ON" : "OFF"}
      </p>
      <audio ref={micRef} autoPlay muted={isLocal} />
      <button onClick={() => webcamStream.pause()}>Pause</button>
      <button onClick={() => webcamStream.resume()}>Resume</button>
      {webcamOn && (
        <ReactPlayer
          //
          playsinline // very very imp prop
          pip={false}
          light={false}
          controls={false}
          muted={true}
          playing={true}
          //
          url={videoStream}
          //
          height={"200px"}
          width={"300px"}
          onError={(err) => {
            console.log(err, "participant video error");
          }}
        />
      )}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Function Controls

src/App.js

function Controls() {
  const { leave, toggleMic, toggleWebcam, toggleScreenShare } = useMeeting();
  return (
    <div>
      <button onClick={() => leave()}>Leave</button>
      <button onClick={() => toggleMic()}>toggleMic</button>
      <button onClick={() => toggleWebcam()}>toggleWebcam</button>
      <button onClick={() => toggleScreenShare()}>screen share</button>

    </div>
  );
}

const PresenterView = ({ presenterId }) => {
  const { screenShareStream, screenShareOn } = useParticipant(presenterId);

  //Creating a media stream from the screen share stream
  const mediaStream = useMemo(() => {
    if (screenShareOn && screenShareStream) {
      const mediaStream = new MediaStream();
      mediaStream.addTrack(screenShareStream.track);
      return mediaStream;
    }
  }, [screenShareStream, screenShareOn]);

  return (
    <>
      <ReactPlayer
        //
        playsinline // very very imp prop
        playIcon={<></>}
        //
        pip={false}
        light={false}
        controls={false}
        muted={true}
        playing={true}
        //
        url={mediaStream} // passing mediastream here
        //
        height={"100%"}
        width={"100%"}
        onError={(err) => {
          console.log(err, "presenter video error");
        }}
      />
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

Create a MeetingView

Let's focus on implementing the MeetingView component. This component is at the core of your application, responsible for displaying the meeting interface, including participant videos, audio, and other essential information.

With the MeetingView in place, you'll have a central hub for all your meeting-related activities. Let's get to work! πŸš€

src/App.js

function MeetingView(props) {
  const [joined, setJoined] = useState(null);
  const { join, presenterId } = useMeeting();
  const { participants } = useMeeting({
    onMeetingJoined: () => {
      setJoined("JOINED");
    },
    onMeetingLeft: () => {
      props.onMeetingLeave();
    },
  });
  const joinMeeting = () => {
    setJoined("JOINING");
    join();
  };

  return (
    <div className="container">
      <h3>Meeting Id: {props.meetingId}</h3>
      {joined && joined == "JOINED" ? (
        <div>
          <Controls />
          {[...participants.keys()].map((participantId) => (
            <ParticipantView
              participantId={participantId}
              key={participantId}
            />
          ))}
          {presenterId && <PresenterView presenterId={presenterId} />}
        </div>
      ) : joined && joined == "JOINING" ? (
        <p>Joining the meeting...</p>
      ) : (
        <button onClick={joinMeeting}>Join</button>
      )}
    </div>
  );
}

function App() {
  const [meetingId, setMeetingId] = useState(null);

  const getMeetingAndToken = async (id) => {
    const meetingId =
      id == null ? await createMeeting({ token: authToken }) : id;
    setMeetingId(meetingId);
  };

  const onMeetingLeave = () => {
    setMeetingId(null);
  };

  return authToken && meetingId ? (
    <MeetingProvider
      config={{
        meetingId,
        micEnabled: true,
        webcamEnabled: true,
        name: "C.V. Raman",
      }}
      token={authToken}
    >
      <MeetingConsumer>
        {() => (
          <MeetingView meetingId={meetingId} onMeetingLeave={onMeetingLeave} />
        )}
      </MeetingConsumer>
    </MeetingProvider>
  ) : (
    <JoinScreen getMeetingAndToken={getMeetingAndToken} />
  );
}
Enter fullscreen mode Exit fullscreen mode

Running the Application

Now that you've implemented all the components, you can run the application using the following command:

npm start

Enter fullscreen mode Exit fullscreen mode

This will start the development server, and you can access your video conferencing app in a web browser.

Conclusion:

Congratulations! You've just wrapped up building your video calling app with screen sharing using React. If you're eager to expand your app's capabilities with features like chat messaging, screen sharing, polls, and more, our documentation is the place to explore.

Should you encounter any hiccups during implementation, don't fret. We've got you covered with examples on GitHub, or you can always reach out to us on our Discord community for assistance.

Well done on your app-building journey! πŸŽ‰

Top comments (0)