Skip to main content
Version: 1.x.x

Quick Start for Interactive Live Streaming in React Native

VideoSDK empowers you to seamlessly integrate interactive live streaming features into your React Native application in minutes. While built for meetings, the SDK is easily adaptable for live streaming with support for up to 100 hosts/co-hosts and 2,000 viewers in real-time. Perfect for social use cases, this guide will walk you through integrating live streaming into your app.

info

For standard live streaming with 6-7 second latency and playback support, 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 Native
  • React Native VideoSDK
  • Have Node and NPM installed on your device.
  • Android Studio or Xcode installed
  • Basic understanding of Hooks (useState, useRef, useEffect)
  • React Context API (optional)

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 Native app

Create a new React Native app using the below command.

$ npx @react-native-community/cli init videosdkIlsReactNativeApp
$ cd videosdkIlsReactNativeApp

Install VideoSDK and Dependencies

Install the VideoSDK along with react-native-safe-area-context, which is used to render the app within the device's safe area. Ensure that you are in your project directory before running this command.

npm install "@videosdk.live/react-native-sdk"  "@videosdk.live/react-native-incallmanager" "react-native-safe-area-context"

Project Structure

Directory Structure
  root
├── node_modules
├── android
├── ios
├── App.js
├── api.js
├── index.js

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

You will be working on these files:

  • api.js: Responsible for handling API calls such as generating unique streamId and token
  • App.js: Responsible for rendering container and joining the Live Stream.

Project Configuration

Expo Setup (Essential for Expo Developers)

If you are developing with Expo, setting up VideoSDK is mandatory. Follow this guide to configure your Expo environment.

Android Setup

AndroidManifest.xml
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cool.app"
>
<!-- Give all the required permissions to app -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Needed to communicate with already-paired Bluetooth devices. (Legacy up to Android 11) -->
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />

<!-- Needed to communicate with already-paired Bluetooth devices. (Android 12 upwards)-->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
</manifest>
android/app/build.gradle
  dependencies {
implementation project(':rnwebrtc')
}
android/settings.gradle
include ':rnwebrtc'
project(':rnwebrtc').projectDir = new File(rootProject.projectDir, '../node_modules/@videosdk.live/react-native-webrtc/android')
MainApplication.kt
import live.videosdk.rnwebrtc.WebRTCModulePackage

class MainApplication : Application(), ReactApplication {

override val reactHost: ReactHost by lazy {
getDefaultReactHost(
context = applicationContext,
packageList =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
add(WebRTCModulePackage())
},
)
}

override fun onCreate() {
super.onCreate()
loadReactNative(this)
}
}
note

This MainApplication.kt matches the template of recent React Native versions — verified against the React Native 0.87 template that npx @react-native-community/cli init currently produces. On an older React Native version, add WebRTCModulePackage() to the package list in your project's existing MainApplication file instead — for example inside getPackages() in the Java template.

iOS Setup

To update CocoaPods, you can reinstall the gem using the following command:

$ sudo gem install cocoapods

Select Your_Xcode_Project/TARGETS/BuildSettings, in Header Search Paths, add "$(SRCROOT)/../node_modules/@videosdk.live/react-native-incall-manager/ios/RNInCallManager"

Podfile
pod 'react-native-webrtc', :path => '../node_modules/@videosdk.live/react-native-webrtc'
note

Your Podfile's default platform :ios, min_ios_version_supported line already satisfies react-native-webrtc's minimum iOS requirement.

After updating the Podfile, you need to install the pods by running the following command:

pod install

Add the following lines to your info.plist file located at (project folder/ios/projectname/info.plist):

ios/projectname/info.plist
<key>NSCameraUsageDescription</key>
<string>Camera permission description</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone permission description</string>

Register Service

Register VideoSDK services in your root index.js file for the initialization service.

index.js
import { AppRegistry } from "react-native";
import App from "./App";
import { name as appName } from "./app.json";
import { register } from "@videosdk.live/react-native-sdk";

register();

AppRegistry.registerComponent(appName, () => App);

Step 1: Get started with API.js

Prior to moving on, you must create an API request to generate a unique streamId. 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
// Generate your token at https://app.videosdk.live/api-keys and paste it below.
export const authToken = "";

// API call to create stream
export const createStream = async ({ token }) => {
try {
const res = await fetch(`https://api.videosdk.live/v2/rooms`, {
method: "POST",
headers: {
authorization: `${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
});

if (!res.ok) {
throw new Error(`Failed to create stream: ${res.status}`);
}

//Destructuring the streamId from the response
const { roomId: streamId } = await res.json();
return streamId;
} catch (error) {
console.error("createStream failed", error);
throw error;
}
};

Step 2: Initialize and Join the Live Stream

To set up the wireframe of App.js, you'll use VideoSDK Hooks and Context Providers, which include:

  • MeetingProvider: The context provider for managing live streams. Accepts config and token as props and makes them accessible to all nested components.
  • useMeeting: A hook providing APIs to manage live streams, like joining, leaving, and toggling the mic or webcam.
  • useParticipant: A hook to handle properties and events for a specific participant, such as their name, webcam, and mic streams.

Before proceeding, let's understand the two modes of a Live Stream:

1. SEND_AND_RECV (For Host or Co-host):
  • Designed primarily for the Host or Co-host.
  • Allows sending and receiving media.
  • Hosts can broadcast their audio/video and interact directly with the audience.
2. RECV_ONLY (For Audience):
  • Tailored for the Audience.
  • Enables receiving media shared by the Host.
  • Audience members can view and listen but cannot share their own media.
ILS Mode Demonstration

The Join Screen allows users to either create a new Live Stream or join an existing one as a host or audience, using these actions:

  1. Join as Host: Allows the user to join an existing Live Stream using the provided streamId with the SEND_AND_RECV mode, enabling full host privileges.

  2. Join as Audience: Enables the user to join an existing Live Stream using the provided streamId with the RECV_ONLY mode, allowing view-only access.

  3. Create Live Stream as Host: Lets the user initiate a new Live Stream in SEND_AND_RECV mode, granting full host controls.

App.js
import React, { useState } from "react";
import {
View,
Text,
TextInput,
TouchableOpacity,
StyleSheet,
Alert,
FlatList,
} from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import {
MeetingProvider,
useMeeting,
useParticipant,
Constants,
register,
RTCView,
MediaStream,
} from "@videosdk.live/react-native-sdk";
import { authToken, createStream } from "./api";

register();

// Join Screen - Handles joining or creating a live stream
function JoinView({ initializeStream, setMode }) {
const [streamId, setStreamId] = useState("");

const handleAction = async (mode) => {
// Sets the mode (Host or Audience) and initializes the stream
setMode(mode);
await initializeStream(streamId);
};

return (
<SafeAreaView style={styles.container}>
<TouchableOpacity
style={styles.button}
onPress={() => handleAction(Constants.modes.SEND_AND_RECV)}
>
<Text style={styles.buttonText}>Create Live Stream as Host</Text>
</TouchableOpacity>
<Text style={styles.separatorText}>---------- OR ----------</Text>
<TextInput
style={styles.input}
placeholder="Enter Stream Id"
onChangeText={setStreamId}
/>
<TouchableOpacity
style={styles.button}
onPress={() => handleAction(Constants.modes.SEND_AND_RECV)}
>
<Text style={styles.buttonText}>Join as Host</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => handleAction(Constants.modes.RECV_ONLY)}
>
<Text style={styles.buttonText}>Join as Audience</Text>
</TouchableOpacity>
</SafeAreaView>
);
}

// Live Stream Container - Placeholder for the live stream container
function LSContainer(props) {
return null;
}

// Main App Component - Handles the app flow and stream lifecycle
function AppContent() {
const [streamId, setStreamId] = useState(null); // Holds the current stream ID
const [mode, setMode] = useState(Constants.modes.SEND_AND_RECV); // Holds the current user mode (Host or Audience)

const initializeStream = async (id) => {
if (!authToken) {
console.error("PLEASE PROVIDE TOKEN IN api.js FROM app.videosdk.live");
return;
}
// Creates a new stream if no ID is provided or uses the given stream ID
const newStreamId = id || (await createStream({ token: authToken }));
setStreamId(newStreamId);
};

const onStreamLeave = () => setStreamId(null); // Resets the stream state on leave

return authToken && streamId ? (
<MeetingProvider
config={{
meetingId: streamId,
micEnabled: true, // Enables microphone by default
webcamEnabled: true, // Enables webcam by default
name: "John Doe", // Default participant name
mode,
defaultCamera: "front",
}}
token={authToken}
>
{/* Renders the live stream container if a stream is active */}
<LSContainer streamId={streamId} onLeave={onStreamLeave} />
</MeetingProvider>
) : (
// Renders the join view if no stream is active
<JoinView initializeStream={initializeStream} setMode={setMode} />
);
}

function App() {
return (
<SafeAreaProvider>
<AppContent />
</SafeAreaProvider>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#F6F6FF",
justifyContent: "center",
paddingHorizontal: 12,
},
button: {
backgroundColor: "#1178F8",
padding: 12,
marginTop: 14,
borderRadius: 6,
alignItems: "center",
},
buttonText: {
color: "white",
fontSize: 16,
},
input: {
padding: 12,
borderWidth: 1,
borderRadius: 6,
fontStyle: "italic",
marginVertical: 10,
},
controls: {
flexDirection: "row",
justifyContent: "space-around",
marginVertical: 16,
flexWrap: "wrap",
},
noMedia: {
backgroundColor: "grey",
height: 300,
justifyContent: "center",
alignItems: "center",
marginVertical: 8,
marginHorizontal: 8,
},
noMediaText: {
fontSize: 16,
},
separatorText: {
alignSelf: "center",
fontSize: 22,
marginVertical: 16,
fontStyle: "italic",
color: "grey",
},
});

export default App;

Output

Step 3: Setting Up Live Stream View and Container

This step implements the Live Stream View and Container components to manage and display HOST participants:

  1. StreamView Component:

    • Displays participants in SEND_AND_RECV mode using the Participant component.
    • Includes LSControls for managing the session.
  2. LSContainer Component:

    • Manages joining the stream via join from useMeeting.
    • Shows the StreamView after joining or a Join Stream button otherwise.
App.js
// Component to manage live stream container and session joining
function LSContainer({ streamId, onLeave }) {
const [joined, setJoined] = useState(false); // Track if the user has joined the stream

const { join } = useMeeting({
onMeetingJoined: () => setJoined(true), // Set `joined` to true when successfully joined
onMeetingLeft: onLeave, // Handle the leave stream event
onError: (error) => Alert.alert("Error", error.message), // Display an alert on encountering an error
});

const handleJoin = async () => {
try {
await join();
} catch (error) {
console.error("Failed to join stream", error);
}
};

return (
<SafeAreaView style={styles.container}>
{streamId ? (
<Text style={{ fontSize: 18, padding: 12 }}>Stream Id :{streamId}</Text>
) : null}
{/* Show the stream view if joined, otherwise display the "Join Stream" button */}
{joined ? (
<StreamView />
) : (
<TouchableOpacity style={styles.button} onPress={handleJoin}>
<Text style={styles.buttonText}>Join Stream</Text>
</TouchableOpacity>
)}
</SafeAreaView>
);
}

// Component to display the live stream view
function StreamView() {
const { participants } = useMeeting(); // Access participants using the VideoSDK useMeeting hook
const participantsArrId = Array.from(participants.entries())
.filter(
([_, participant]) => participant.mode === Constants.modes.SEND_AND_RECV
)
.map(([key]) => key);

return (
<View style={{ flex: 1 }}>
<FlatList
data={participantsArrId}
renderItem={({ item }) => {
return <Participant participantId={item} />;
}}
/>
<LSControls />
</View>
);
}

function Participant() {
return null;
}

function LSControls() {
return null;
}

Output

Step 4: Render Individual Participant's View

This step component displays an individual participant's audio and video streams. It uses the useParticipant hook to retrieve the participant's data.

We don't need to manage the audio stream manually; RTCView handles it seamlessly under the hood.

App.js
// Component to render video streams for a participant
function Participant({ participantId }) {
const { webcamStream, webcamOn } = useParticipant(participantId);

return webcamOn && webcamStream ? (
<RTCView
streamURL={new MediaStream([webcamStream?.track]).toURL()}
objectFit={"cover"}
style={{
height: 300,
marginVertical: 8,
marginHorizontal: 8,
}}
/>
) : (
<View style={styles.noMedia}>
<Text style={styles.noMediaText}>NO MEDIA</Text>
</View>
);
}

Output

Step 5: Implement Live Stream Controls

This step provides buttons to leave the stream, toggle the microphone and camera, and switch between Host Mode (send/receive streams) and Audience Mode (receive streams only). It uses useMeeting to access these functionalities.

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

App.js
// Component for managing stream controls
function LSControls() {
const { leave, toggleMic, toggleWebcam, changeMode, meeting } = useMeeting(); // Access methods

const currentMode = meeting.localParticipant.mode; // Get the current participant's mode

const handleLeave = async () => {
try {
await leave();
} catch (error) {
console.error("Failed to leave stream", error);
}
};

const handleToggleMic = async () => {
try {
await toggleMic();
} catch (error) {
console.error("Failed to toggle mic", error);
}
};

const handleToggleWebcam = async () => {
try {
await toggleWebcam();
} catch (error) {
console.error("Failed to toggle webcam", error);
}
};

const handleChangeMode = async () => {
const nextMode =
currentMode === Constants.modes.SEND_AND_RECV
? Constants.modes.RECV_ONLY
: Constants.modes.SEND_AND_RECV;
try {
await changeMode(nextMode);
} catch (error) {
console.error("Failed to change mode", error);
}
};

return (
<View style={styles.controls}>
<TouchableOpacity style={styles.button} onPress={handleLeave}>
<Text style={styles.buttonText}>Leave</Text>
</TouchableOpacity>
{currentMode === Constants.modes.SEND_AND_RECV && (
<>
<TouchableOpacity style={styles.button} onPress={handleToggleMic}>
<Text style={styles.buttonText}>Toggle Mic</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={handleToggleWebcam}>
<Text style={styles.buttonText}>Toggle Camera</Text>
</TouchableOpacity>
</>
)}
<TouchableOpacity style={styles.button} onPress={handleChangeMode}>
<Text style={styles.buttonText}>
{currentMode === Constants.modes.SEND_AND_RECV
? "Switch to Audience"
: "Switch to Host"}
</Text>
</TouchableOpacity>
</View>
);
}

Output

Final Output

tip

You can checkout the complete quick start example here.

Got a Question? Ask us on discord