video sdk logo

Login

React Native Audio Rooms Tutorial

Build drop-in audio rooms for iOS and Android from a single React Native codebase. Speaker and listener roles, raise hand, and moderation come from the SDK, while native audio routing switches cleanly between earpiece, loudspeaker, and Bluetooth.
React Native Audio Rooms Tutorial

Trusted by 300+ global business & tech leaders

Build an Audio-Only Room on iOS and Android

Learn how to build an audio-only room in React Native using the VideoSDK React Native SDK. This tutorial covers room setup, speaker and listener modes, raise-hand requests, and mute, moderation, and device-routing controls.


Prerequisites

Before you begin, make sure you have:

  • A VideoSDK developer account
  • Node.js >= 22.11.0
  • NPM v10+ (comes installed with newer Node versions)
  • Android Studio or Xcode installed
  • A basic understanding of React Native functional components
  • Familiarity with the useState hook
  • A temporary VideoSDK token from the VideoSDK dashboard for local development

App Architecture

The app will contain two screens:

  1. Join Screen – This screen allows users to either create a meeting or join a predefined meeting.
  2. Meeting Screen – This screen contains a participant list and meeting controls, such as enabling/disabling the microphone and leaving the meeting.

The app uses the following component hierarchy:

Component Architecture
1App
2├── JoinScreen
3└── MeetingProvider
4    └── MeetingView
5        ├── ControlsContainer
6        └── ParticipantList
7            └── ParticipantView × each participant
8

MeetingProvider makes the active meeting available to its descendant components. MeetingView joins the room and reads its participant collection with useMeeting. Each ParticipantView reads one participant's state with useParticipant and displays that participant's name and mic status.

Getting Started with the Code!

Create App

Create a new React Native app using the command below:

Terminal
1npx @react-native-community/cli init AppName
2

For React Native setup, you can follow the Official Documentation.

Install VideoSDK

Install the VideoSDK React Native SDK and the in-call manager package. Make sure you are in your project directory before running this command. Use only the command for your package manager.

NPM

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

Yarn

Yarn
1yarn add "@videosdk.live/react-native-sdk" "@videosdk.live/react-native-incallmanager" "react-native-safe-area-context"
2

Project Structure

Your relevant project files will look like this:

Project Structure
1root/
2├── node_modules/
3├── android/
4├── ios/
5├── App.js
6├── api.js
7└── index.js
8

Project Configuration

Android Setup

Step 1: Add the required permissions in the AndroidManifest.xml file.

Since this tutorial covers audio calling only, the camera permission is not required.

AndroidManifest.xml
1<manifest
2  xmlns:android="http://schemas.android.com/apk/res/android"
3  package="com.cool.app"
4>
5    <!-- Give all the required permissions to app -->
6    <uses-permission android:name="android.permission.INTERNET" />
7    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
8    <!-- Needed to communicate with already-paired Bluetooth devices. (Legacy up to Android 11) -->
9    <uses-permission
10        android:name="android.permission.BLUETOOTH"
11        android:maxSdkVersion="30" />
12    <uses-permission
13        android:name="android.permission.BLUETOOTH_ADMIN"
14        android:maxSdkVersion="30" />
15
16    <!-- Needed to communicate with already-paired Bluetooth devices. (Android 12 upwards)-->
17    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
18    
19    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
20    <uses-permission android:name="android.permission.RECORD_AUDIO" />
21    <uses-permission android:name="android.permission.WAKE_LOCK" />
22</manifest>
23

Step 2: Link the necessary VideoSDK dependencies.

android/app/build.gradle
1dependencies {
2    implementation project(' :rnwebrtc')
3}
4
android/settings.gradle
1include ':rnwebrtc'
2project(':rnwebrtc').projectDir = new File(rootProject.projectDir, '../node_modules/@videosdk.live/react-native-webrtc/android')
3
MainApplication.kt
1import live.videosdk.rnwebrtc.WebRTCModulePackage
2
3class MainApplication : Application(), ReactApplication {
4
5  override val reactHost: ReactHost by lazy {
6    getDefaultReactHost(
7      context = applicationContext,
8      packageList =
9        PackageList(this).packages.apply {
10          // Packages that cannot be autolinked yet can be added manually here, for example:
11          // add(MyReactNativePackage())
12          add(WebRTCModulePackage())
13        },
14    )
15  }
16
17  override fun onCreate() {
18    super.onCreate()
19    loadReactNative(this)
20  }
21}
22

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.

Step 3: Include the following line in your proguard-rules.pro file (optional: if you are using Proguard).

android/app/proguard-rules.pro
1-keep class org.webrtc.** { *; }
2

Step 4: In your build.gradle file, update the minimum OS/SDK version to 24.

android/build.gradle
1buildscript {
2    ext {
3        minSdkVersion = 24
4    }
5}
6

iOS Setup

Step 1: IMPORTANT — Ensure that you are using CocoaPods version 1.10 or later.

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

Terminal
1sudo gem install cocoapods
2

Step 2: Manually link react-native-incall-manager (if it is not linked automatically).

Select Your_Xcode_Project/TARGETS/BuildSettings, in Header Search Paths, add:

1$(SRCROOT)/../node_modules/@videosdk.live/react-native-incall-manager/ios/RNInCallManager
2

Step 3: Change the path of react-native-webrtc using the following line:

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

Step 4: Install pods.

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

Terminal
1pod install
2

Step 5: Declare permissions in Info.plist.

Add the following lines to your info.plist file, located at project folder/ios/projectname/info.plist. Since this tutorial covers audio calling only, the camera usage description is not required.

ios/projectname/info.plist
1<key>NSMicrophoneUsageDescription</key>
2<string>Microphone permission description</string>
3

Register Service

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

index.js
1import { AppRegistry } from "react-native";
2import App from "./App";
3import { name as appName } from "./app.json";
4import { register } from "@videosdk.live/react-native-sdk";
5
6register();
7
8AppRegistry.registerComponent(appName, () => App);
9

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 token = "";
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    const { roomId } = await res.json();
21    return roomId;
22  } catch (error) {
23    console.error("createMeeting failed", error);
24    throw error;
25  }
26};
27

Step 2: Wireframe App.js with All the Components

To build up the wireframe of App.js, you need to use VideoSDK Hooks and Context Providers. VideoSDK provides MeetingProvider, MeetingConsumer, useMeeting, and useParticipant hooks.

First you need to understand the 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 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 a meeting such as join, leave, enable/disable mic, 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 and mic status.

The Meeting Context provides a way to listen for any changes that occur when a participant joins the meeting or makes modifications to their microphone and other settings.

Begin by making a few changes to the code in the App.js file.

App.js
1import React, { useState } from "react";
2import {
3  SafeAreaView,
4  TouchableOpacity,
5  Text,
6  TextInput,
7  View,
8  FlatList,
9} from "react-native";
10import {
11  MeetingProvider,
12  useMeeting,
13  useParticipant,
14} from "@videosdk.live/react-native-sdk";
15import { createMeeting, token } from "./api";
16
17function JoinScreen(props) {
18  return null;
19}
20
21function ControlsContainer() {
22  return null;
23}
24
25function MeetingView() {
26  return null;
27}
28
29export default function App() {
30  const [meetingId, setMeetingId] = useState(null);
31
32  const getMeetingId = async (id) => {
33    const meetingId = id == null ? await createMeeting({ token }) : id;
34    setMeetingId(meetingId);
35  };
36
37  return meetingId ? (
38    <SafeAreaView style={{ flex: 1, backgroundColor: "#F6F6FF" }}>
39      <MeetingProvider
40        config={{
41          meetingId,
42          micEnabled: true,
43          webcamEnabled: false,
44          name: "Test User",
45        }}
46        token={token}
47      >
48        <MeetingView />
49      </MeetingProvider>
50    </SafeAreaView>
51  ) : (
52    <JoinScreen getMeetingId={getMeetingId} />
53  );
54}
55

Since this app only handles audio calling, webcamEnabled is set to false and the MediaStream/RTCView video imports used for rendering camera video are left out.

At this stage, the child components intentionally return null. You will replace each placeholder in the next three steps.

Step 3: Implement Join Screen

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

Replace the JoinScreen placeholder in App.js with:

JoinScreen Component
1function JoinScreen(props) {
2  const [meetingVal, setMeetingVal] = useState("");
3
4  return (
5    <SafeAreaView
6      style={{
7        flex: 1,
8        backgroundColor: "#F6F6FF",
9        justifyContent: "center",
10        paddingHorizontal: 6 * 10,
11      }}
12    >
13      <TouchableOpacity
14        onPress={() => {
15          props.getMeetingId();
16        }}
17        style={{ backgroundColor: "#1178F8", padding: 12, borderRadius: 6 }}
18      >
19        <Text style={{ color: "white", alignSelf: "center", fontSize: 18 }}>
20          Create Meeting
21        </Text>
22      </TouchableOpacity>
23      <Text
24        style={{
25          alignSelf: "center",
26          fontSize: 22,
27          marginVertical: 16,
28          fontStyle: "italic",
29          color: "grey",
30        }}
31      >
32        ---------- OR ----------
33      </Text>
34      <TextInput
35        value={meetingVal}
36        onChangeText={setMeetingVal}
37        placeholder={"XXXX-XXXX-XXXX"}
38        style={{
39          padding: 12,
40          borderWidth: 1,
41          borderRadius: 6,
42          fontStyle: "italic",
43        }}
44      />
45      <TouchableOpacity
46        style={{
47          backgroundColor: "#1178F8",
48          padding: 12,
49          marginTop: 14,
50          borderRadius: 6,
51        }}
52        onPress={() => {
53          props.getMeetingId(meetingVal);
54        }}
55      >
56        <Text style={{ color: "white", alignSelf: "center", fontSize: 18 }}>
57          Join Meeting
58        </Text>
59      </TouchableOpacity>
60    </SafeAreaView>
61  );
62}
63

Step 4: Implement Controls

Next, create a ControlsContainer component to manage features such as joining or leaving the meeting and enabling or disabling the mic.

In this step, the useMeeting hook is utilized to acquire all the required methods, such as join(), leave(), and toggleMic().

Replace the ControlsContainer placeholder with:

ControlsContainer Component
1const Button = ({ onPress, buttonText, backgroundColor }) => {
2  return (
3    <TouchableOpacity
4      onPress={onPress}
5      style={{
6        backgroundColor: backgroundColor,
7        justifyContent: "center",
8        alignItems: "center",
9        padding: 12,
10        borderRadius: 4,
11      }}
12    >
13      <Text style={{ color: "white", fontSize: 12 }}>{buttonText}</Text>
14    </TouchableOpacity>
15  );
16};
17
18function ControlsContainer({ join, leave, toggleMic }) {
19  return (
20    <View
21      style={{
22        padding: 24,
23        flexDirection: "row",
24        justifyContent: "space-between",
25      }}
26    >
27      <Button
28        onPress={() => {
29          join();
30        }}
31        buttonText={"Join"}
32        backgroundColor={"#1178F8"}
33      />
34      <Button
35        onPress={() => {
36          toggleMic();
37        }}
38        buttonText={"Toggle Mic"}
39        backgroundColor={"#1178F8"}
40      />
41      <Button
42        onPress={() => {
43          leave();
44        }}
45        buttonText={"Leave"}
46        backgroundColor={"#FF0000"}
47      />
48    </View>
49  );
50}
51

Replace the MeetingView placeholder with:

MeetingView Component
1function ParticipantList() {
2  return null;
3}
4
5function MeetingView() {
6  const { join, leave, toggleMic, meetingId } = useMeeting({});
7
8  return (
9    <View style={{ flex: 1 }}>
10      {meetingId ? (
11        <Text style={{ fontSize: 18, padding: 12 }}>
12          Meeting Id : {meetingId}
13        </Text>
14      ) : null}
15      <ParticipantList />
16      <ControlsContainer join={join} leave={leave} toggleMic={toggleMic} />
17    </View>
18  );
19}
20

Step 5: Render Participant List

After implementing the controls, the next step is to render the joined participants. You can get all the joined participants from the useMeeting hook.

Replace the ParticipantList placeholder with:

ParticipantList Component
1function ParticipantView() {
2  return null;
3}
4
5function ParticipantList({ participants }) {
6  return participants.length > 0 ? (
7    <FlatList
8      data={participants}
9      renderItem={({ item }) => {
10        return <ParticipantView participantId={item} />;
11      }}
12    />
13  ) : (
14    <View
15      style={{
16        flex: 1,
17        backgroundColor: "#F6F6FF",
18        justifyContent: "center",
19        alignItems: "center",
20      }}
21    >
22      <Text style={{ fontSize: 20 }}>Press Join button to enter meeting.</Text>
23    </View>
24  );
25}
26

Update MeetingView to pass the participant IDs into ParticipantList:

MeetingView Component
1function MeetingView() {
2  // Get `participants` from useMeeting Hook
3  const { join, leave, toggleMic, participants } = useMeeting({});
4  const participantsArrId = [...participants.keys()];
5
6  return (
7    <View style={{ flex: 1 }}>
8      <ParticipantList participants={participantsArrId} />
9      <ControlsContainer join={join} leave={leave} toggleMic={toggleMic} />
10    </View>
11  );
12}
13

Step 6: Handling Participant's Audio

Before rendering each participant, it helps to understand the useParticipant hook.

useParticipant Hook

The useParticipant hook is responsible for handling all the properties and events of one particular participant joined in the meeting. It takes participantId as an argument and returns, among other things, the participant's displayName, micOn status, and whether the participant isLocal.

useParticipant Hook Example
1const { displayName, micOn, isLocal } = useParticipant(participantId);
2

Because the VideoSDK React Native SDK, together with @videosdk.live/react-native-incallmanager set up earlier, automatically routes and plays a remote participant's microphone audio through the device's call audio session, ParticipantView does not need to manually attach an audio stream. It only needs to display the participant's name and current mic status.

Replace the ParticipantView placeholder with:

ParticipantView Component
1function ParticipantView({ participantId }) {
2  const { displayName, micOn, isLocal } = useParticipant(participantId);
3
4  return (
5    <View
6      style={{
7        backgroundColor: "#F6F6FF",
8        paddingVertical: 16,
9        paddingHorizontal: 12,
10        marginVertical: 4,
11        marginHorizontal: 8,
12        borderRadius: 6,
13        flexDirection: "row",
14        justifyContent: "space-between",
15        alignItems: "center",
16      }}
17    >
18      <Text style={{ fontSize: 16 }}>
19        {displayName} {isLocal ? "(You)" : ""}
20      </Text>
21      <Text style={{ fontSize: 14, color: micOn ? "green" : "grey" }}>
22        {micOn ? "Mic ON" : "Mic OFF"}
23      </Text>
24    </View>
25  );
26}
27

Run Your Application

Terminal
1npm run android // Android
2npm run ios     // iOS
3

Your completed app can now:

  • Create a new VideoSDK room
  • Join an existing room by meeting ID
  • Publish microphone audio
  • Automatically play remote participants' microphone audio
  • View joined participants and their live mic status
  • Toggle the local microphone
  • Leave the meeting and return to the join screen

Tip: Stuck anywhere? Check out the combined audio and video example code on GitHub. Expo users can refer to the Expo example code on GitHub.

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