video sdk logo

Login

React HTTP Live Streaming (HLS) Tutorial

Create a host-and-viewer live streaming experience with VideoSDK. In this practical React tutorial, you will publish host audio and video, start an HLS broadcast, and play the generated .m3u8 stream in the browser with hls.js.
React HTTP Live Streaming (HLS) Tutorial

Trusted by 300+ global business & tech leaders

Go Live from Your React App with HLS

Learn how to add HLS live streaming to a React app using the VideoSDK React SDK. This tutorial covers starting and stopping a stream, host and viewer modes, and HLS playback at scale.


Info: For low-latency interactive live streaming (under 100ms), follow this documentation.

Prerequisites

Before proceeding, ensure that your development environment meets the following requirements:

  • Video SDK Developer Account (Not having one, follow Video SDK Dashboard)
  • Basic understanding of React
  • React Video SDK
  • Have Node and NPM installed on your device.
  • Basic understanding of Hooks (useState, useRef, useEffect)
  • React Context API (optional)

Important: One should have a VideoSDK account to generate token. Visit VideoSDK dashboard to generate token

Getting Started with the Code!

Follow the steps to create the environment necessary to add live streaming into your app. You can also find the code sample for quickstart here.

Create new react app

Create a new React App using the below command.

Terminal
1npm create vite@latest videosdk-rtc-react-app -- --template react
2

Install Video SDK

Install the VideoSDK using the below-mentioned npm command. Make sure you are in your react app directory before you run this command.

NPM
1npm install "@videosdk.live/react-sdk"
2
Yarn
1yarn add "@videosdk.live/react-sdk"
2

Structure of the project

Your project structure should look like this.

Project Structure
1   root
2   ├── node_modules
3   ├── public
4   ├── src
5   │    ├── API.js
6   │    ├── App.jsx
7   │    ├── main.jsx
8   ├── package.json
9   .    .
10

You are going to use functional components to leverage react's reusable component architecture. There will be components for users, videos and controls (mic, camera, leave) over the video.

App Architecture

The App will contain a container component which includes a user component with videos. Each video component will have control buttons for mic, camera , leave meeting and HLS.

You will be working on these files:

  • API.js: Responsible for handling API calls such as generating unique meetingId and token
  • App.jsx: Responsible for rendering container and joining the meeting.

Architecture for Speaker

quick-start-speaker-architechture

Architecture for Viewer

quick-start-viewer-architechture

Step 1: Get started with API.js

Prior to moving on, you must create an API request to generate a unique meetingId. You will need an authentication token, which you can create either through the videosdk-rtc-api-server-examples or directly from the VideoSDK Dashboard for developers.

API.js
1// Generate your token at https://app.videosdk.live/api-keys and paste it below.
2export const authToken = "";
3
4// API call to create meeting
5export const createMeeting = async ({ token }) => {
6  try {
7    const res = await fetch(`https://api.videosdk.live/v2/rooms`, {
8      method: "POST",
9      headers: {
10        authorization: `${token}`,
11        "Content-Type": "application/json",
12      },
13      body: JSON.stringify({}),
14    });
15
16    if (!res.ok) {
17      throw new Error(`Failed to create meeting: ${res.status}`);
18    }
19
20    //Destructuring the roomId from the response
21    const { roomId } = await res.json();
22    return roomId;
23  } catch (error) {
24    console.error("createMeeting failed", error);
25    throw error;
26  }
27};
28

Step 2: Wireframe App.jsx with all the components

To build up wireframe of App.jsx, you will be using VideoSDK Hooks and Context Providers. VideoSDK provides MeetingProvider, MeetingConsumer, useMeeting and useParticipant hooks. Let's understand each of them.

First we will explore Context Provider and Consumer. Context is primarily used when some data needs to be accessible by many components at different nesting levels.

  • MeetingProvider: This is the Context Provider. It accepts value config and token as props. The Provider component accepts a value prop to be passed to consuming components that are descendants of this Provider. One Provider can be connected to many consumers. Providers can be nested to override values deeper within the tree.
  • MeetingConsumer: This is the Context Consumer. All consumers that are descendants of a Provider will re-render whenever the Provider's value prop changes.
  • useMeeting: This is the meeting hook API. It includes all the information related to meeting such as join, leave, enable/disable mic or webcam etc.
  • useParticipant: This is the participant hook API. It is responsible for handling all the events and props related to one particular participant such as name, webcamStream, micStream etc.

Meeting Context helps to listen on all the changes when participant joines meeting or changes mic or camera etc.

Let's get started with change couple of lines of code in App.jsx

App.jsx
1import "./App.css";
2import React, { useEffect, useRef, useState } from "react";
3import {
4  MeetingProvider,
5  MeetingConsumer,
6  useMeeting,
7  useParticipant,
8  Constants,
9  VideoPlayer,
10} from "@videosdk.live/react-sdk";
11
12import { authToken, createMeeting } from "./API";
13
14function JoinScreen({ getMeetingAndToken, setMode }) {
15  return null;
16}
17
18function ParticipantView(props) {
19  return null;
20}
21
22function Controls() {
23  return null;
24}
25
26function SpeakerView() {
27  return null;
28}
29
30function ViewerView() {
31  return null;
32}
33
34function Container(props) {
35  return null;
36}
37
38function App() {
39  const [meetingId, setMeetingId] = useState(null);
40
41  //State to handle the mode of the participant i.e. SEND_AND_RECV or SIGNALLING_ONLY
42  const [mode, setMode] = useState("SEND_AND_RECV");
43
44  //You have to get the MeetingId from the API created earlier
45  const getMeetingAndToken = async (id) => {
46    if (!authToken) {
47      console.error("PLEASE PROVIDE TOKEN IN API.js FROM app.videosdk.live");
48      return;
49    }
50    const meetingId =
51      id == null ? await createMeeting({ token: authToken }) : id;
52    setMeetingId(meetingId);
53  };
54
55  const onMeetingLeave = () => {
56    setMeetingId(null);
57  };
58
59  return authToken && meetingId ? (
60    <MeetingProvider
61      config={{
62        meetingId,
63        micEnabled: true,
64        webcamEnabled: true,
65        name: "C.V. Raman",
66        //This will be the mode of the participant SEND_AND_RECV or SIGNALLING_ONLY
67        mode: mode,
68      }}
69      token={authToken}
70    >
71      <MeetingConsumer>
72        {() => (
73          <Container meetingId={meetingId} onMeetingLeave={onMeetingLeave} />
74        )}
75      </MeetingConsumer>
76    </MeetingProvider>
77  ) : (
78    <JoinScreen getMeetingAndToken={getMeetingAndToken} setMode={setMode} />
79  );
80}
81
82export default App;
83

Step 3: Implement Join Screen

Join screen will serve as a medium to either schedule a new meeting or join an existing one as a host or a viewer.

This functionality will have 3 buttons:

1. Join as Host: When this button is clicked, the person will join the meeting with the entered meetingId as HOST.

2. Join as Viewer: When this button is clicked, the person will join the meeting with the entered meetingId as SIGNALLING_ONLY.

3. Create Meeting: When this button is clicked, the person will join a new meeting as HOST.

JoinScreen Component
1function JoinScreen({ getMeetingAndToken, setMode }) {
2  const [meetingId, setMeetingId] = useState(null);
3  //Set the mode of joining participant and set the meeting id or generate new one
4  const onClick = async (mode) => {
5    setMode(mode);
6    await getMeetingAndToken(meetingId);
7  };
8  return (
9    <div className="container">
10      <button onClick={() => onClick("SEND_AND_RECV")}>Create Meeting</button>
11      <br />
12      <br />
13      {" or "}
14      <br />
15      <br />
16      <input
17        type="text"
18        placeholder="Enter Meeting Id"
19        onChange={(e) => {
20          setMeetingId(e.target.value);
21        }}
22      />
23      <br />
24      <br />
25      <button onClick={() => onClick("SEND_AND_RECV")}>Join as Host</button>
26      {" | "}
27      <button onClick={() => onClick("SIGNALLING_ONLY")}>Join as Viewer</button>
28    </div>
29  );
30}
31

Step 4: Implement Container Component

Next step is to create a container to manage features such as join, leave, mute, unmute, start and stop HLS for the HOST and to display an HLS Player for the viewer.

You need to determine the mode of the localParticipant, if its SEND_AND_RECV, display the SpeakerView component otherwise show the ViewerView component.

Container Component
1function Container(props) {
2  const [joined, setJoined] = useState(null);
3  //Get the method which will be used to join the meeting.
4  const { join } = useMeeting();
5  const mMeeting = useMeeting({
6    //callback for when a meeting is joined successfully
7    onMeetingJoined: () => {
8      setJoined("JOINED");
9    },
10    //callback for when a meeting is left
11    onMeetingLeft: () => {
12      props.onMeetingLeave();
13    },
14    //callback for when there is an error in a meeting
15    onError: (error) => {
16      alert(error.message);
17    },
18  });
19  const joinMeeting = async () => {
20    setJoined("JOINING");
21    try {
22      await join();
23    } catch (error) {
24      console.error("Failed to join meeting", error);
25      setJoined(null);
26    }
27  };
28
29  return (
30    <div className="container">
31      <h3>Meeting Id: {props.meetingId}</h3>
32      {joined && joined === "JOINED" ? (
33        mMeeting.localParticipant.mode === Constants.modes.SEND_AND_RECV ? (
34          <SpeakerView />
35        ) : mMeeting.localParticipant.mode ===
36          Constants.modes.SIGNALLING_ONLY ? (
37          <ViewerView />
38        ) : null
39      ) : joined && joined === "JOINING" ? (
40        <p>Joining the meeting...</p>
41      ) : (
42        <button onClick={joinMeeting}>Join</button>
43      )}
44    </div>
45  );
46}
47

Step 5: Implement SpeakerView

Next step is to create SpeakerView and Controls components to manage features such as join, leave, mute and unmute.

  1. You have to retrieve all the participants using the useMeeting hook and filter them based on the mode set to SEND_AND_RECV ensuring that only Speakers are displayed on the screen.
SpeakerView
1function SpeakerView() {
2  //Get the participants and HLS State from useMeeting
3  const { participants, hlsState } = useMeeting();
4
5  //Filtering the host/speakers from all the participants
6  const speakers = [...participants.values()].filter((participant) => {
7    return participant.mode === Constants.modes.SEND_AND_RECV;
8  });
9  return (
10    <div>
11      <p>Current HLS State: {hlsState}</p>
12      {/* Controls for the meeting */}
13      <Controls />
14
15      {/* Rendring all the HOST participants */}
16      {speakers.map((participant) => (
17        <ParticipantView participantId={participant.id} key={participant.id} />
18      ))}
19    </div>
20  );
21}
22
23function Container(){
24  ...
25
26  const mMeeting = useMeeting({
27    onMeetingJoined: async () => {
28      //Pin the local participant if he joins in SEND_AND_RECV mode
29      if (mMeetingRef.current.localParticipant.mode === "SEND_AND_RECV") {
30        try {
31          await mMeetingRef.current.localParticipant.pin();
32        } catch (error) {
33          console.error("Failed to pin the local participant", error);
34        }
35      }
36      setJoined("JOINED");
37    },
38    ...
39  });
40
41  //Create a ref to meeting object so that when used inside the
42  //Callback functions, meeting state is maintained
43  const mMeetingRef = useRef(mMeeting);
44  useEffect(() => {
45    mMeetingRef.current = mMeeting;
46  }, [mMeeting]);
47
48  return <>...</>;
49}
50
  1. You have to add the Controls component which will allow the participant to toggle their media.

From v1.0.0, all these methods are asynchronous and return a Promise, so awaitthem and handle failures with try / catch. For details, see the Migration Guide.

Controls Component
1function Controls() {
2  const { leave, toggleMic, toggleWebcam, startHls, stopHls } = useMeeting();
3
4  const handleLeave = async () => {
5    try {
6      await leave();
7    } catch (error) {
8      console.error("Failed to leave meeting", error);
9    }
10  };
11
12  const handleToggleMic = async () => {
13    try {
14      await toggleMic();
15    } catch (error) {
16      console.error("Failed to toggle mic", error);
17    }
18  };
19
20  const handleToggleWebcam = async () => {
21    try {
22      await toggleWebcam();
23    } catch (error) {
24      console.error("Failed to toggle webcam", error);
25    }
26  };
27
28  const handleStopHls = async () => {
29    try {
30      await stopHls();
31    } catch (error) {
32      console.error("Failed to stop HLS", error);
33    }
34  };
35
36  return (
37    <div>
38      <button onClick={handleLeave}>Leave</button>
39      &emsp;|&emsp;
40      <button onClick={handleToggleMic}>toggleMic</button>
41      <button onClick={handleToggleWebcam}>toggleWebcam</button>
42      &emsp;|&emsp;
43      <button
44        onClick={async () => {
45          //Start the HLS in SPOTLIGHT mode and PIN as
46          //priority so only speakers are visible in the HLS stream
47          try {
48            await startHls({
49              layout: {
50                type: "SPOTLIGHT",
51                priority: "PIN",
52                gridSize: "20",
53              },
54              theme: "DARK",
55              mode: "video-and-audio",
56              quality: "high",
57              orientation: "landscape",
58            });
59          } catch (error) {
60            console.error("Failed to start HLS", error);
61          }
62        }}
63      >
64        Start HLS
65      </button>
66      <button onClick={handleStopHls}>Stop HLS</button>
67    </div>
68  );
69}
70
  1. You need to then create the ParticipantView to display the participant's name and media. To play the media, use the micStream from the useParticipant hook.
ParticipantView
1function ParticipantView(props) {
2  const micRef = useRef(null);
3  const { micStream, webcamOn, micOn, isLocal, displayName } = useParticipant(
4    props.participantId
5  );
6
7  //Playing the audio in the <audio>
8  useEffect(() => {
9    if (micRef.current) {
10      if (micOn && micStream) {
11        const mediaStream = new MediaStream();
12        mediaStream.addTrack(micStream.track);
13
14        micRef.current.srcObject = mediaStream;
15        micRef.current
16          .play()
17          .catch((error) =>
18            console.error("micElem.current.play() failed", error)
19          );
20      } else {
21        micRef.current.srcObject = null;
22      }
23    }
24  }, [micStream, micOn]);
25
26  return (
27    <div>
28      <p>
29        Participant: {displayName} | Webcam: {webcamOn ? "ON" : "OFF"} | Mic:{" "}
30        {micOn ? "ON" : "OFF"}
31      </p>
32      <audio ref={micRef} autoPlay muted={isLocal} />
33      {webcamOn && (
34        <VideoPlayer
35          participantId={props.participantId} // Required
36          type="video" // "video" or "share"
37          containerStyle={{
38            height: "200px",
39            width: "300px",
40          }}
41          className="h-full"
42          classNameVideo="h-full"
43          videoStyle={{}}
44        />
45      )}
46    </div>
47  );
48}
49

Step 6: Implement ViewerView

When the host initiates the live streaming, viewers will be able to watch it.

To implement the player view, you have to use hls.js. It will be helpful for playing the HLS stream.

Begin by adding this package.

NPM
1$ npm install hls.js
2
Yarn
1$ yarn add hls.js
2

With hls.js installed, you can now get the hlsUrls from the useMeeting hook which will be used to play the HLS in the player.

ViewerView
1//importing hls.js
2import Hls from "hls.js";
3
4function ViewerView() {
5  // States to store downstream url and current HLS state
6  const playerRef = useRef(null);
7  //Getting the hlsUrls
8  const { hlsUrls, hlsState } = useMeeting();
9
10  //Playing the HLS stream when the playbackHlsUrl is present and it is playable
11  useEffect(() => {
12    if (hlsUrls.playbackHlsUrl && hlsState === "HLS_PLAYABLE") {
13      if (Hls.isSupported()) {
14        const hls = new Hls({
15          maxLoadingDelay: 1, // max video loading delay used in automatic start level selection
16          defaultAudioCodec: "mp4a.40.2", // default audio codec
17          maxBufferLength: 0, // If buffer length is/becomes less than this value, a new fragment will be loaded
18          maxMaxBufferLength: 1, // Hls.js will never exceed this value
19          startLevel: 0, // Start playback at the lowest quality level
20          startPosition: -1, // set -1 playback will start from intialtime = 0
21          maxBufferHole: 0.001, // 'Maximum' inter-fragment buffer hole tolerance that hls.js can cope with when searching for the next fragment to load.
22          highBufferWatchdogPeriod: 0, // if media element is expected to play and if currentTime has not moved for more than highBufferWatchdogPeriod and if there are more than maxBufferHole seconds buffered upfront, hls.js will jump buffer gaps, or try to nudge playhead to recover playback.
23          nudgeOffset: 0.05, // In case playback continues to stall after first playhead nudging, currentTime will be nudged evenmore following nudgeOffset to try to restore playback. media.currentTime += (nb nudge retry -1)*nudgeOffset
24          nudgeMaxRetry: 1, // Max nb of nudge retries before hls.js raise a fatal BUFFER_STALLED_ERROR
25          maxFragLookUpTolerance: 0.1, // This tolerance factor is used during fragment lookup.
26          liveSyncDurationCount: 1, // if set to 3, playback will start from fragment N-3, N being the last fragment of the live playlist
27          abrEwmaFastLive: 1, // Fast bitrate Exponential moving average half-life, used to compute average bitrate for Live streams.
28          abrEwmaSlowLive: 3, // Slow bitrate Exponential moving average half-life, used to compute average bitrate for Live streams.
29          abrEwmaFastVoD: 1, // Fast bitrate Exponential moving average half-life, used to compute average bitrate for VoD streams
30          abrEwmaSlowVoD: 3, // Slow bitrate Exponential moving average half-life, used to compute average bitrate for VoD streams
31          maxStarvationDelay: 1, // ABR algorithm will always try to choose a quality level that should avoid rebuffering
32        });
33
34        let player = document.querySelector("#hlsPlayer");
35
36        hls.loadSource(hlsUrls.playbackHlsUrl);
37        hls.attachMedia(player);
38      } else {
39        if (typeof playerRef.current?.play === "function") {
40          playerRef.current.src = hlsUrls.playbackHlsUrl;
41          playerRef.current.play();
42        }
43      }
44    }
45  }, [hlsUrls, hlsState]);
46
47  return (
48    <div>
49      {/* Showing message if HLS is not started or is stopped by HOST */}
50      {hlsState !== "HLS_PLAYABLE" ? (
51        <div>
52          <p>HLS has not started yet or is stopped</p>
53        </div>
54      ) : (
55        hlsState === "HLS_PLAYABLE" && (
56          <div>
57            <video
58              ref={playerRef}
59              id="hlsPlayer"
60              autoPlay={true}
61              controls
62              style={{ width: "100%", height: "100%" }}
63              playsInline
64              muted={true}
65              playing
66              onError={(err) => {
67                console.log(err, "hls video error");
68              }}
69            ></video>
70          </div>
71        )
72      )}
73    </div>
74  );
75}
76

Final Output

You have completed the implementation of a customised live streaming app in ReactJS using VideoSDK. To explore more features, go through Basic and Advanced features.

Tip: You can checkout the complete quick start example here.

Summarize with

ChatGPTClaudePerplexityGoogleGrok
Prefer VideoSDK on Google

Table of Contents

Start Building Now With $20 Free Balance

No credit card required to start.

Documentation →Github Repo →

Share This Article

TwitterLinkedInFacebookHacker News

Get started for free today

Grab your API keys and start building with free $20 included in your account. Need enterprise-grade security or custom workloads? Talk to an expert!

Book a demo

Video SDK Logo

United States

28 Geary St, Suite 650,
San Francisco, CA 94108, United States

India flag

India

18th Floor, 1812, The Junomoneta Tower,
Adajan-Hazira Rd, Surat, Gujarat 395009, India

Video SDK Logo
Video SDK Logo
Video SDK Logo
Video SDK Logo
SOLUTIONS

Video KYC

Video Banking

Virtual Claim

Video MER

Telehealth

Astrology

Gaming

Dating

Live Commerce

Auto Proctoring

Interview-as-a-service

Virtual Events

Live Audio Streaming

Ed-Tech

PRODUCTS

AI-Agents

Real-time Audio & Video SDK

Interactive Live Streaming SDK

Real-time Transcription SDK

Character SDK

Open Source Examples

SUCCESS STORIES

Examedi

Coderschool

TYHO

ForagerOne

Immigo

DEVELOPERS

Documentation

Code Samples

Developer Updates

Developer Hub

TOP ARTICLES

What is WebRTC?

Build a React Native Video Calling App

Build a Flutter Video Calling App

RESOURCES

The Protocol by Video SDK

AI Apps

Creator Program

LEGAL

Terms Of Service

Privacy Policy

Cookie Notice

CCPA Notice

Subprocessors

DPA

RSS

COMPANY

Contact Us

Pricing

Support

Blog

Press Kit