React Native Quickstart Guide
Overview
This guide will walk you through simple instructions to create a Video Conferencing app using the 100ms React Native SDK and test it using an Emulator or your Mobile Phone. Ensure that you use the latest versions of the SDKs to follow the instructions in this guide.
Package | Version |
---|---|
@100mslive/react-native-room-kit | |
@100mslive/react-native-hms | |
@100mslive/react-native-video-plugin |
Please check our Basic Concepts Guide to understand the concepts like Templates, Rooms, Peers, Roles, Tracks, etc.
This Guide contains instructions for two approaches to get you started with 100ms React Native SDK:
- Create a Sample App — Instructions to create a React Native app quickly with a complete Code Sample.
- Building Step-by-Step — Instructions to walk you through the implementation of the app in a step-by-step manner.
Check out the full-featured Advanced Example app implementation in the 100ms React Native SDK GitHub repository showcasing multiple features provided by 100ms.
You can also check our Basic Sample App to get started quickly.
You can download the fully featured Example app on your iOS & Android Devices.
📲 Download the Example iOS app here: https://testflight.apple.com/join/v4bSIPad
🤖 Download the Example Android app here: https://appdistribution.firebase.dev/i/7b7ab3b30e627c35
Create a sample app
This section contains instructions to create a simple React Native Video Conferencing app. We will help you with instructions to understand the project setup and complete code sample to implement this quickly.
Prerequisites
- A 100ms account if you don't have one already.
- Working React Native Development Environment for React Native CLI
- Familiar with basics of React Native.
- VS code or any other IDE / code editor
Create a React Native app
Once you have the prerequisites, follow the steps below to create a React Native app. This guide will use VS code but you can use any code editor or IDE.
-
Open your Terminal and navigate to directory/folder where you would like to create your app.
-
Run the below command to create React Native app: ​
npx react-native init HMSSampleApp --version 0.68.5 --npm && cd ./HMSSampleApp
-
Once the app is created, open it in VS code.
-
Test run your app
a. Build the App ​
npx react-native run-ios --simulator="iPhone 14"
b. Start Metro Bundler if it is not already started ​
npx react-native start
or follow instructions printed in Terminal to start the Metro Bundler or Run the Application.
Install the Dependencies
After the Test run of your app is successful, you can install 100ms React Native package in your app.
Since the app requires Camera & Microphone permissions, a package for requesting these permissions from users should also be installed. We recommend using the react-native-permissions package.
npm install --save @100mslive/react-native-hms react-native-permissions
Complete code example
Now that your project setup is complete, let's replace the code in the App.js
file with the complete code sample below -
import React, { useState, useRef, useEffect, useCallback } from 'react'; import { SafeAreaView, FlatList, StatusBar, Text, View, TouchableHighlight, ActivityIndicator, Alert, } from 'react-native'; import { PERMISSIONS, request, requestMultiple, RESULTS } from 'react-native-permissions'; import { HMSSDK, HMSUpdateListenerActions, HMSConfig, HMSTrackType, HMSTrackUpdate, HMSPeerUpdate } from '@100mslive/react-native-hms'; /** * Take Room Code from Dashbaord for this sample app. * For more info, Check out {@link https://www.100ms.live/docs/react-native/v2/get-started/token#get-room-code-from-100ms-dashboard | Room Code} */ const ROOM_CODE = ''; // PASTE ROOM CODE FROM DASHBOARD HERE /** * using `ROOM_CODE` is recommended over `AUTH_TOKEN` approach * * Take Auth Token from Dashbaord for this sample app. * For more info, Check out {@link https://www.100ms.live/docs/react-native/v2/foundation/security-and-tokens | Token Concept} */ const AUTH_TOKEN = ''; // PASTE AUTH TOKEN FROM DASHBOARD HERE const USERNAME = 'Test User'; //#region Screens const App = () => { const [joinRoom, setJoinRoom] = useState(false); const navigate = useCallback((screen) => setJoinRoom(screen === 'RoomScreen'), []); return ( <SafeAreaView style={{ flex: 1, backgroundColor: '#EFF7FF' }}> <StatusBar barStyle={'dark-content'} /> {joinRoom ? ( <RoomScreen navigate={navigate} /> ) : ( <HomeScreen navigate={navigate} /> )} </SafeAreaView> ); }; export default App; const HomeScreen = ({ navigate }) => { // Function to handle "Join Room" button press const handleJoinPress = async () => { // Checking Device Permissions const permissionsGranted = await checkPermissions([ PERMISSIONS.ANDROID.CAMERA, PERMISSIONS.ANDROID.RECORD_AUDIO, PERMISSIONS.ANDROID.BLUETOOTH_CONNECT ]); if (permissionsGranted) { navigate('RoomScreen'); } else { console.log('Permission Not Granted!'); } }; return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <TouchableHighlight onPress={handleJoinPress} underlayColor="#143466" style={{ paddingHorizontal: 20, paddingVertical: 12, backgroundColor: '#2471ED', borderRadius: 8 }}> <Text style={{ fontSize: 20, color: '#ffffff' }}>Join Room</Text> </TouchableHighlight> </View> ); }; const RoomScreen = ({ navigate }) => { /** * `usePeerTrackNodes` hook takes care of setting up {@link HMSSDK | HMSSDK} instance, joining room and adding all required event listeners. * It gives us: * 1. peerTrackNodes - This is a list of {@link PeerTrackNode}, we can use this list to render local and remote peer tiles. * 2. loading - We can show loader while Room Room join is under process. * 3. leaveRoom - This is a function that can be called on a button press to leave room and go back to Welcome screen. */ const { peerTrackNodes, loading, leaveRoom, hmsInstanceRef } = usePeerTrackNodes({ navigate }); const HmsView = hmsInstanceRef.current?.HmsView; const _keyExtractor = (item) => item.id; // `_renderItem` function returns a Tile UI for each item which is `PeerTrackNode` object const _renderItem = ({ item }) => { const { peer, track } = item; return ( <View style={{ height: 300, margin: 8, borderRadius: 20, overflow: 'hidden', backgroundColor: '#A0C3D2' }}> {/* Checking if we have "HmsView" component, valid trackId and "track is not muted" */} {HmsView && track && track.trackId && !track.isMute() ? ( // To Render Peer Live Videos, We can use HMSView // For more info about its props and usage, Check out {@link https://www.100ms.live/docs/react-native/v2/features/render-video | Render Video} <HmsView trackId={track.trackId} mirror={peer.isLocal} style={{ width: '100%', height: '100%' }} /> ) : ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <View style={{ width: 100, height: 100, borderRadius: 50, alignItems: 'center', justifyContent: 'center', backgroundColor: '#FD8A8A' }}> <Text style={{ textAlign: 'center', fontSize: 28, fontWeight: 'bold', textTransform: 'uppercase' }}> {peer.name .split(' ') .map((item) => item[0]) .join('')} </Text> </View> </View> )} </View> ); }; const handleRoomEnd = () => { leaveRoom(); navigate('HomeScreen'); }; return ( <View style={{ flex: 1 }}> {loading ? ( // Showing loader while Join is under process <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <ActivityIndicator size={'large'} color="#2471ED" /> </View> ) : ( <View style={{ flex: 1, position: 'relative' }}> {peerTrackNodes.length > 0 ? ( // Rendering list of Peers <FlatList centerContent={true} data={peerTrackNodes} showsVerticalScrollIndicator={false} keyExtractor={_keyExtractor} renderItem={_renderItem} contentContainerStyle={{ paddingBottom: 120, flexGrow: Platform.OS === 'android' ? 1 : undefined, justifyContent: Platform.OS === 'android' ? 'center' : undefined }} /> ) : ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text style={{ fontSize: 28, marginBottom: 32 }}>Welcome!</Text> <Text style={{ fontSize: 16 }}>You’re the first one here.</Text> <Text style={{ fontSize: 16 }}> Sit back and relax till the others join. </Text> </View> )} {/* Button to Leave Room */} <TouchableHighlight onPress={handleRoomEnd} underlayColor="#6e2028" style={{ position: 'absolute', bottom: 40, alignSelf: 'center', backgroundColor: '#CC525F', width: 60, height: 60, borderRadius: 30, alignItems: 'center', justifyContent: 'center' }}> <Text style={{ textAlign: 'center', color: '#ffffff', fontWeight: 'bold' }}> Leave Room </Text> </TouchableHighlight> </View> )} </View> ); }; //#endregion Screens /** * Sets up HMSSDK instance, Adds required Event Listeners * Checkout Quick Start guide to know things covered {@link https://www.100ms.live/docs/react-native/v2/guides/quickstart | Quick Start Guide} */ export const usePeerTrackNodes = ({ navigate }) => { const hmsInstanceRef = useRef(null); // We will save `hmsInstance` in this ref const [loading, setLoading] = useState(true); const [peerTrackNodes, setPeerTrackNodes] = useState([]); // Use this state to render Peer Tiles /** * Handles Room leave process */ const handleRoomLeave = async () => { try { const hmsInstance = hmsInstanceRef.current; if (!hmsInstance) { return Promise.reject('HMSSDK instance is null'); } // Removing all registered listeners hmsInstance.removeAllListeners(); /** * Leave Room. For more info, Check out {@link https://www.100ms.live/docs/react-native/v2/features/leave | Leave Room} */ const leaveResult = await hmsInstance.leave(); console.log('Leave Success: ', leaveResult); /** * Free/Release Resources. For more info, Check out {@link https://www.100ms.live/docs/react-native/v2/features/release-resources | Release Resources} */ const destroyResult = await hmsInstance.destroy(); console.log('Destroy Success: ', destroyResult); // Removing HMSSDK instance hmsInstanceRef.current = null; } catch (error) { console.log('Leave or Destroy Error: ', error); } }; /** * Handles Join Update received from {@link HMSUpdateListenerActions.ON_JOIN} event listener * Receiving This event means User (that is Local Peer) has successfully joined room * @param {Object} data - object which has room object * @param {Object} data.room - current {@link HMSRoom | room} object */ const onJoinSuccess = (data) => { /** * Checkout {@link HMSLocalPeer | HMSLocalPeer} Class */ const { localPeer } = data.room; // Creating or Updating Local Peer Tile // `updateNode` function updates "Track and Peer objects" in PeerTrackNodes and returns updated list. // if none exist then we are "creating a new PeerTrackNode with the received Track and Peer" setPeerTrackNodes((prevPeerTrackNodes) => updateNode({ nodes: prevPeerTrackNodes, peer: localPeer, track: localPeer.videoTrack, createNew: true }) ); // Turning off loading state on successful Room Room join setLoading(false); }; /** * Handles Peer Updates received from {@link HMSUpdateListenerActions.ON_PEER_UPDATE} event listener * @param {Object} data - This has updated peer and update type * @param {HMSPeer} data.peer - Updated Peer * @param {HMSPeerUpdate} data.type - Update Type */ const onPeerListener = ({ peer, type }) => { // We will create Tile for the Joined Peer when we receive `HMSUpdateListenerActions.ON_TRACK_UPDATE` event. // Note: We are chosing to not create Tiles for Peers which does not have any tracks if (type === HMSPeerUpdate.PEER_JOINED) return; if (type === HMSPeerUpdate.PEER_LEFT) { // Remove all Tiles which has peer same as the peer which just left the room. // `removeNodeWithPeerId` function removes peerTrackNodes which has given peerID and returns updated list. setPeerTrackNodes((prevPeerTrackNodes) => removeNodeWithPeerId(prevPeerTrackNodes, peer.peerID) ); return; } if (peer.isLocal) { // Updating the LocalPeer Tile. // `updateNodeWithPeer` function updates Peer object in PeerTrackNodes and returns updated list. // if none exist then we are "creating a new PeerTrackNode for the updated Peer". setPeerTrackNodes((prevPeerTrackNodes) => updateNodeWithPeer({ nodes: prevPeerTrackNodes, peer, createNew: true }) ); return; } if ( type === HMSPeerUpdate.ROLE_CHANGED || type === HMSPeerUpdate.METADATA_CHANGED || type === HMSPeerUpdate.NAME_CHANGED || type === HMSPeerUpdate.NETWORK_QUALITY_UPDATED ) { // Ignoring these update types because we want to keep this implementation simple. return; } }; /** * Handles Track Updates received from {@link HMSUpdateListenerActions.ON_TRACK_UPDATE} event listener * @param {Object} data - This has updated track with peer and update type * @param {HMSPeer} data.peer - Peer * @param {HMSTrack} data.track - Peer Track * @param {HMSTrackUpdate} data.type - Update Type */ const onTrackListener = ({ peer, track, type }) => { // on TRACK_ADDED update // We will update Tile with the track or // create new Tile for with the track and peer if (type === HMSTrackUpdate.TRACK_ADDED && track.type === HMSTrackType.VIDEO) { // We will only update or create Tile "with updated track" when track type is Video. // Tiles without Video Track are already respresenting Peers with or without Audio. // Updating the Tiles with Track and Peer. // `updateNode` function updates "Track and Peer objects" in PeerTrackNodes and returns updated list. // if none exist then we are "creating a new PeerTrackNode with the received Track and Peer". setPeerTrackNodes((prevPeerTrackNodes) => updateNode({ nodes: prevPeerTrackNodes, peer, track, createNew: true }) ); return; } // on TRACK_MUTED or TRACK_UNMUTED updates, We will update Tiles (PeerTrackNodes) if (type === HMSTrackUpdate.TRACK_MUTED || type === HMSTrackUpdate.TRACK_UNMUTED) { // We will only update Tile "with updated track" when track type is Video. if (track.type === HMSTrackType.VIDEO) { // Updating the Tiles with Track and Peer. // `updateNode` function updates "Track and Peer objects" in PeerTrackNodes and returns updated list. // Note: We are not creating new PeerTrackNode object. setPeerTrackNodes((prevPeerTrackNodes) => updateNode({ nodes: prevPeerTrackNodes, peer, track }) ); } else { // Updating the Tiles with Peer. // `updateNodeWithPeer` function updates Peer object in PeerTrackNodes and returns updated list. // Note: We are not creating new PeerTrackNode object. setPeerTrackNodes((prevPeerTrackNodes) => updateNodeWithPeer({ nodes: prevPeerTrackNodes, peer }) ); } return; } if (type === HMSTrackUpdate.TRACK_REMOVED) { // If non-regular track, or // both regular video and audio tracks are removed // Then we will remove Tiles (PeerTrackNodes) with removed track and received peer return; } /** * For more info about Degrade/Restore. check out {@link https://www.100ms.live/docs/react-native/v2/features/auto-video-degrade | Auto Video Degrade} */ if (type === HMSTrackUpdate.TRACK_RESTORED || type === HMSTrackUpdate.TRACK_DEGRADED) { return; } }; /** * Handles Errors received from {@link HMSUpdateListenerActions.ON_ERROR} event listener * @param {HMSException} error * * For more info, Check out {@link https://www.100ms.live/docs/react-native/v2/features/error-handling | Error Handling} */ const onErrorListener = (error) => { setLoading(false); console.log(`${error?.code} ${error?.description}`); }; // Effect to handle HMSSDK initialization and Listeners Setup useEffect(() => { const joinRoom = async () => { try { setLoading(true); /** * creating {@link HMSSDK} instance to join room * For more info, Check out {@link https://www.100ms.live/docs/react-native/v2/features/join#join-a-room | Join a Room} */ const hmsInstance = await HMSSDK.build(); // Saving `hmsInstance` in ref hmsInstanceRef.current = hmsInstance; let token = AUTH_TOKEN; // if `AUTH_TOKEN` is not valid, generate auth token from `ROOM_CODE` if (!token) { token = await hmsInstance.getAuthTokenByRoomCode(ROOM_CODE); } /** * Adding HMSSDK Event Listeners before calling Join method on HMSSDK instance * For more info, Check out - * {@link https://www.100ms.live/docs/react-native/v2/features/join#update-listener | Adding Event Listeners before Join}, * {@link https://www.100ms.live/docs/react-native/v2/features/event-listeners | Event Listeners}, * {@link https://www.100ms.live/docs/react-native/v2/features/event-listeners-enums | Event Listeners Enums} */ hmsInstance.addEventListener(HMSUpdateListenerActions.ON_JOIN, onJoinSuccess); hmsInstance.addEventListener(HMSUpdateListenerActions.ON_PEER_UPDATE, onPeerListener); hmsInstance.addEventListener(HMSUpdateListenerActions.ON_TRACK_UPDATE, onTrackListener); hmsInstance.addEventListener(HMSUpdateListenerActions.ON_ERROR, onErrorListener); /** * Joining Room. For more info, Check out {@link https://www.100ms.live/docs/react-native/v2/features/join#join-a-room | Join a Room} */ hmsInstance.join(new HMSConfig({ authToken: token, username: USERNAME })); } catch (error) { navigate('HomeScreen'); console.error(error); Alert.alert('Error', 'Check your console to see error logs!'); } }; joinRoom(); // When effect unmounts for any reason, We are calling leave function return () => { handleRoomLeave(); }; }, [navigate]); return { loading, leaveRoom: handleRoomLeave, peerTrackNodes, hmsInstanceRef }; }; //#region Utilities /** * Function to check permissions * @param {string[]} permissions * @returns {boolean} all permissions granted or not */ export const checkPermissions = async (permissions) => { try { if (Platform.OS === 'ios') { return true; } const requiredPermissions = permissions.filter( (permission) => permission.toString() !== PERMISSIONS.ANDROID.BLUETOOTH_CONNECT ); const results = await requestMultiple(requiredPermissions); let allPermissionsGranted = true; for (let permission in requiredPermissions) { if (!(results[requiredPermissions[permission]] === RESULTS.GRANTED)) { allPermissionsGranted = false; } console.log( `${requiredPermissions[permission]} : ${results[requiredPermissions[permission]]}` ); } // Bluetooth Connect Permission handling if ( permissions.findIndex( (permission) => permission.toString() === PERMISSIONS.ANDROID.BLUETOOTH_CONNECT ) >= 0 ) { const bleConnectResult = await request(PERMISSIONS.ANDROID.BLUETOOTH_CONNECT); console.log(`${PERMISSIONS.ANDROID.BLUETOOTH_CONNECT} : ${bleConnectResult}`); } return allPermissionsGranted; } catch (error) { console.log(error); return false; } }; /** * returns `uniqueId` for a given `peer` and `track` combination */ export const getPeerTrackNodeId = (peer, track) => { return peer.peerID + (track?.source ?? HMSTrackSource.REGULAR); }; /** * creates `PeerTrackNode` object for given `peer` and `track` combination */ export const createPeerTrackNode = (peer, track) => { let isVideoTrack = false; if (track && track?.type === HMSTrackType.VIDEO) { isVideoTrack = true; } const videoTrack = isVideoTrack ? track : undefined; return { id: getPeerTrackNodeId(peer, track), peer: peer, track: videoTrack }; }; /** * Removes all nodes which has `peer` with `id` same as the given `peerID`. */ export const removeNodeWithPeerId = (nodes, peerID) => { return nodes.filter((node) => node.peer.peerID !== peerID); }; /** * Updates `peer` of `PeerTrackNode` objects which has `peer` with `peerID` same as the given `peerID`. * * If `createNew` is passed as `true` and no `PeerTrackNode` exists with `id` same as `uniqueId` generated from given `peer` and `track` * then new `PeerTrackNode` object will be created. */ export const updateNodeWithPeer = (data) => { const { nodes, peer, createNew = false } = data; const peerExists = nodes.some((node) => node.peer.peerID === peer.peerID); if (peerExists) { return nodes.map((node) => { if (node.peer.peerID === peer.peerID) { return { ...node, peer }; } return node; }); } if (!createNew) return nodes; if (peer.isLocal) { return [createPeerTrackNode(peer), ...nodes]; } return [...nodes, createPeerTrackNode(peer)]; }; /** * Removes all nodes which has `id` same as `uniqueId` generated from given `peer` and `track`. */ export const removeNode = (nodes, peer, track) => { const uniqueId = getPeerTrackNodeId(peer, track); return nodes.filter((node) => node.id !== uniqueId); }; /** * Updates `track` and `peer` of `PeerTrackNode` objects which has `id` same as `uniqueId` generated from given `peer` and `track`. * * If `createNew` is passed as `true` and no `PeerTrackNode` exists with `id` same as `uniqueId` generated from given `peer` and `track` * then new `PeerTrackNode` object will be created */ export const updateNode = (data) => { const { nodes, peer, track, createNew = false } = data; const uniqueId = getPeerTrackNodeId(peer, track); const nodeExists = nodes.some((node) => node.id === uniqueId); if (nodeExists) { return nodes.map((node) => { if (node.id === uniqueId) { return { ...node, peer, track }; } return node; }); } if (!createNew) return nodes; if (peer.isLocal) { return [createPeerTrackNode(peer, track), ...nodes]; } return [...nodes, createPeerTrackNode(peer, track)]; }; //#endregion Utility
Fetch token to Join the Room
Fetch token using room-code method (Recommended)
We can get the authentication token using room-code from meeting URL.
Let's understand the subdomain and code from the sample URL
In this sample URL: http://100ms-rocks.app.100ms.live/meeting/abc-defg-hij
- Subdomain is
100ms-rocks
- Room code is
abc-defg-hij
Now to get the room-code from meeting URL, we can write our own logic or use the getCode
method from here
To generate token we will be using getAuthTokenByRoomCode
method available on HMSSDK
instance. This method has roomCode
as a required
parameter and userId
& endpoint
as optional parameter.
Let's checkout the implementation:
try { const hmsInstance = await HMSSDK.build(); // Saving `hmsInstance` in ref hmsInstanceRef.current = hmsInstance; /** * `getAuthTokenByRoomCode` returns a promise which is resolved with "auth token" * checkout {@link https://www.100ms.live/docs/react-native/v2/how--to-guides/install-the-sdk/hmssdk#what-does-destroy-method-do} */ const token = await hmsInstance.getAuthTokenByRoomCode('YOUR_ROOM_CODE'); // ... Adding HMSSDK Event Listeners before calling Join method on HMSSDK instance ... /** * Create `HMSConfig` with the above auth token and username */ const hmsConfig = new HMSConfig({ authToken: token, username: USERNAME }); /** * Joining Room. For more info, Check out {@link https://www.100ms.live/docs/react-native/v2/features/join#join-a-room | Join a Room} */ hmsInstance.join(hmsConfig); } catch (error) { // Handle errors here }
Get temporary token from dashboard
To test audio/video functionality, you need to connect to a 100ms Room. Please check the following steps for the same -
- Navigate to your 100ms dashboard or create an account if you don't have one.
- Use the
Video Conferencing Starter Kit
to create a room with a default template assigned to it to test this app quickly. - Go to the Rooms page in your dashboard, click on the
Room Id
of the room you created above, and click on theJoin Room
button on the top right. - You will see 100ms demo URLs for the roles created when you deployed the starter kit; you can click on the 'key' icon to copy the token and update the
AUTH_TOKEN
variable in "App.js" file.Token from 100ms dashboard is for testing purposes only, For production applications you must generate tokens on your own server. Refer to the Management Token section in Authentication and Tokens guide for more information.
Test the app
Follow the instructions in one of the tabs below based on the target platform you wish to test the app.
Native file changes
- Allow camera, recording audio and internet permissions
Add the below snippet in the info.plist
file -
​
<key>NSCameraUsageDescription</key> <string>Please allow access to Camera to enable video conferencing</string> <key>NSMicrophoneUsageDescription</key> <string>Please allow access to Microphone to enable video conferencing</string> <key>NSLocalNetworkUsageDescription</key> <string>Please allow access to network usage to enable video conferencing</string>
Add the below snippet in the ios/Podfile
file -
​
target 'HMSSampleApp' do ...
permissions_path = '../node_modules/react-native-permissions/ios'pod 'Permission-Camera', :path => "#{permissions_path}/Camera"pod 'Permission-Microphone', :path => "#{permissions_path}/Microphone"... end
- Change ios target platform version to '13.0' in the
ios/Podfile
file ​
require_relative '../node_modules/react-native/scripts/react_native_pods' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
platform :ios, '13.0'install! 'cocoapods', :deterministic_uuids => false
Build and run the app
Once you've made the above changes, your app is ready for testing. You can build the app and run it in a simulator.
- Install pods ​
cd ios && pod install && cd ../
- Build the App ​
npx react-native run-ios --simulator="iPhone 14"
- Start Metro Bundler if it is not already started ​
npx react-native start
If you see any permission related error, then check out react-native-permissions
library setup guide
Now, after you click join
, you should be able to see yourself (iOS simulator doesn't support actual video, you can connect an actual device to see your video in real-time). You can join the room using a browser as the second peer to check audio/video transactions between two or more peers.
Building step-by-step
In this section, We'll walk through what the above code does.
Below is the diagram which shows the common SDK implementation lifecycle. We start with requesting the Camera & Microphone permissions from user and end with leaving the 100ms Room.
Now we will look at code and explaination for each step in implementation lifecycle.
Handle device permissions
We need permission from the user to access the media from the user's device. We must urge the user to grant permission to access camera, microphone, and bluetooth devices. We use the react-native-permissions library that provides a cross-platform (iOS, Android) API to request permissions and check their status.
Please ensure to add permissions declarations in the AndroidManifest.xml
file for Android and info.plist
file for iOS. Check test the app section for more information.
import {PERMISSIONS, requestMultiple, RESULTS} from 'react-native-permissions'; export const checkPermissions = async (permissions) => { try { if (Platform.OS === 'ios') { return true; } const requiredPermissions = permissions.filter(permission => permission.toString() !== PERMISSIONS.ANDROID.BLUETOOTH_CONNECT); const results = await requestMultiple(requiredPermissions); let allPermissionsGranted = true; for (let permission in requiredPermissions) { if (!(results[requiredPermissions[permission]] === RESULTS.GRANTED)) { allPermissionsGranted = false; } console.log(`${requiredPermissions[permission]} : ${results[requiredPermissions[permission]]}`); } // Bluetooth Connect Permission handling if ( permissions.findIndex(permission => permission.toString() === PERMISSIONS.ANDROID.BLUETOOTH_CONNECT) >= 0 ) { const bleConnectResult = await request(PERMISSIONS.ANDROID.BLUETOOTH_CONNECT); console.log(`${PERMISSIONS.ANDROID.BLUETOOTH_CONNECT} : ${bleConnectResult}`); } return allPermissionsGranted; } catch (error) { console.log(error); return false; } }; ... // Function to handle "Join Room" button press const handleJoinPress = async () => { // Checking Device Permissions const permissionsGranted = await checkPermissions([ PERMISSIONS.ANDROID.CAMERA, PERMISSIONS.ANDROID.RECORD_AUDIO, PERMISSIONS.ANDROID.BLUETOOTH_CONNECT, ]); if (permissionsGranted) { navigate('RoomScreen'); } else { console.log('Permission Not Granted!'); } };
In the above code snippet, first, we have checkPermissions
function which uses requestMultiple
function from react-native-permissions
library to request permissions from device. Then we have handleJoinPress
function which calls checkPermissions
function to request Camera, Microphone and Bluetooth permissions on "Join Room" button press.
Implement Join screen
This section will help you create the join screen user interface. To keep it simple for the quickstart, we have not created many UI elements. You can refer to the sample app implementation for a complete Preview/Join user interface.
const HomeScreen = ({ navigate }) => { // Function to handle "Join Room" button press const handleJoinPress = async () => { ... }; return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <TouchableHighlight onPress={handleJoinPress} underlayColor='#143466' style={{ paddingHorizontal: 20, paddingVertical: 12, backgroundColor: '#2471ED', borderRadius: 8 }} > <Text style={{ fontSize: 20, color: '#ffffff' }}>Join Room</Text> </TouchableHighlight> </View> ); };
In the above code snippet, We are implementing UI for "Join Room" button in Home screen.
Initializing the SDK
100ms SDK provides various events which the client apps can subscribe to. These events notify about updates happening in the room after a user has joined.
After requesting required Audio & Video permissions from the user, we are ready to initialize the 100ms SDK to subscribe to updates.
// Effect to handle HMSSDK initialization and Listeners Setup useEffect(() => { const joinRoom = async () => { setLoading(true); /** * creating {@link HMSSDK} instance to join room * For more info, Check out {@link https://www.100ms.live/docs/react-native/v2/features/join#join-a-room | Join a Room} */ const hmsInstance = await HMSSDK.build(); // Saving `hmsInstance` in ref hmsInstanceRef.current = hmsInstance; ... }; joinRoom(); // When effect unmounts for any reason, we are calling leave function to exit the 100ms Room return () => { handleRoomLeave(); }; }, []);
In above code snippet, we have a useEffect
hook which initializes the HMSSDK
by calling static build
method.
Listen to Join and Peer Updates
The 100ms SDK emits:
HMSUpdateListenerActions.ON_JOIN
event when the user has joined the Room successfully, andHMSUpdateListenerActions.ON_PEER_UPDATE
event when any change happens for any Peer in the Room.
Our application must subscribe to these events to get the updates.
Check out the Event Listeners docs to know more about events emitted by the SDK.
// Subscribing to Join updates hmsInstance.addEventListener(HMSUpdateListenerActions.ON_JOIN, onJoinSuccess); // Subscribing to Peer updates hmsInstance.addEventListener(HMSUpdateListenerActions.ON_PEER_UPDATE, onPeerListener); const onJoinSuccess = (data) => { /** * Checkout {@link HMSLocalPeer | HMSLocalPeer} Class */ const { localPeer } = data.room; // Creating or Updating Local Peer Tile // `updateNode` function updates "Track and Peer objects" in PeerTrackNodes and returns updated list. // if none exist then we are "creating a new PeerTrackNode with the received Track and Peer" setPeerTrackNodes((prevPeerTrackNodes) => updateNode({ nodes: prevPeerTrackNodes, peer: localPeer, track: localPeer.videoTrack, createNew: true }) ); // Turning off loading state on successful Room Room join setLoading(false); }; const onPeerListener = ({ peer, type }) => { // We will create Tile for the Joined Peer when we receive `HMSUpdateListenerActions.ON_TRACK_UPDATE` event. // Note: We are chosing to not create Tiles for Peers which does not have any tracks if (type === HMSPeerUpdate.PEER_JOINED) return; if (type === HMSPeerUpdate.PEER_LEFT) { // Remove all Tiles which has peer same as the peer which just left the room. // `removeNodeWithPeerId` function removes peerTrackNodes which has given peerID and returns updated list. setPeerTrackNodes((prevPeerTrackNodes) => removeNodeWithPeerId(prevPeerTrackNodes, peer.peerID) ); return; } if (peer.isLocal) { // Updating the LocalPeer Tile. // `updateNodeWithPeer` function updates Peer object in PeerTrackNodes and returns updated list. // if none exist then we are "creating a new PeerTrackNode for the updated Peer". setPeerTrackNodes((prevPeerTrackNodes) => updateNodeWithPeer({ nodes: prevPeerTrackNodes, peer, createNew: true }) ); return; } if ( type === HMSPeerUpdate.ROLE_CHANGED || type === HMSPeerUpdate.METADATA_CHANGED || type === HMSPeerUpdate.NAME_CHANGED || type === HMSPeerUpdate.NETWORK_QUALITY_UPDATED ) { // Ignoring these update types because we want to keep this implementation simple. return; } };
Listen to Track Updates
The 100ms SDK emits HMSUpdateListenerActions.ON_TRACK_UPDATE
event when any change happens for any Track in the Room. Our application must subscribe to this event to get the track updates.
Check out the Event Listeners docs to know more about events emitted by the SDK.
// Subscribing to Track updates hmsInstance.addEventListener(HMSUpdateListenerActions.ON_TRACK_UPDATE, onTrackListener); const onTrackListener = ({ peer, track, type }) => { // on TRACK_ADDED update // We will update Tile with the track or // create new Tile for with the track and peer if (type === HMSTrackUpdate.TRACK_ADDED && track.type === HMSTrackType.VIDEO) { // We will only update or create Tile "with updated track" when track type is Video. // Tiles without Video Track are already respresenting Peers with or without Audio. // Updating the Tiles with Track and Peer. // `updateNode` function updates "Track and Peer objects" in PeerTrackNodes and returns updated list. // if none exist then we are "creating a new PeerTrackNode with the received Track and Peer". setPeerTrackNodes((prevPeerTrackNodes) => updateNode({ nodes: prevPeerTrackNodes, peer, track, createNew: true }) ); return; } // on TRACK_MUTED or TRACK_UNMUTED updates, We will update Tiles (PeerTrackNodes) if (type === HMSTrackUpdate.TRACK_MUTED || type === HMSTrackUpdate.TRACK_UNMUTED) { // We will only update Tile "with updated track" when track type is Video. if (track.type === HMSTrackType.VIDEO) { // Updating the Tiles with Track and Peer. // `updateNode` function updates "Track and Peer objects" in PeerTrackNodes and returns updated list. // Note: We are not creating new PeerTrackNode object. setPeerTrackNodes((prevPeerTrackNodes) => updateNode({ nodes: prevPeerTrackNodes, peer, track }) ); } else { // Updating the Tiles with Peer. // `updateNodeWithPeer` function updates Peer object in PeerTrackNodes and returns updated list. // Note: We are not creating new PeerTrackNode object. setPeerTrackNodes((prevPeerTrackNodes) => updateNodeWithPeer({ nodes: prevPeerTrackNodes, peer }) ); } return; } if (type === HMSTrackUpdate.TRACK_REMOVED) { // If non-regular track, or // both regular video and audio tracks are removed // Then we will remove Tiles (PeerTrackNodes) with removed track and received peer return; } /** * For more info about Degrade/Restore. check out {@link https://www.100ms.live/docs/react-native/v2/features/auto-video-degrade | Auto Video Degrade} */ if (type === HMSTrackUpdate.TRACK_RESTORED || type === HMSTrackUpdate.TRACK_DEGRADED) { return; } };
Listen to Other Updates
The 100ms SDK emits various other events to handle different scenarios in the app. For example, you can use HMSUpdateListenerActions.ON_ERROR
event to get errors from HMSSDK
.
Check out the Event Listeners docs to know more about events emitted by the SDK.
// Subscribing to Error updates hmsInstance.addEventListener(HMSUpdateListenerActions.ON_ERROR, onErrorListener); const onErrorListener = (error) => { setLoading(false); console.log(`${error?.code} ${error?.description}`); };
Join Room
After adding all required event listeners, you need to create an HMSConfig
instance and use that instance to call the join
method of HMSSDK
instance to join a Room.
Note: An Auth token
is required to authenticate a Room join request from your client-side app. Please ensure to set the AUTH_TOKEN
variable value with correct "Auth Token" by fetching it from your dashboard. Check fetch token to join a room section for more information.
Read more about authentication and tokens in this guide.
You can learn more about joining the Room from Join Room docs.
// Effect to handle HMSSDK initialization and Listeners Setup useEffect(() => { const joinRoom = async () => { setLoading(true); /** * creating {@link HMSSDK} instance to join room * For more info, Check out {@link https://www.100ms.live/docs/react-native/v2/features/join#join-a-room | Join a Room} */ const hmsInstance = await HMSSDK.build(); ... /** * Joining Room. For more info, Check out {@link https://www.100ms.live/docs/react-native/v2/features/join#join-a-room | Join a Room} */ hmsInstance.join(new HMSConfig({ authToken: token, username: USERNAME })); }; joinRoom(); // When effect unmounts for any reason, We are calling leave function return () => { handleRoomLeave(); }; }, []);
In above code snippet, we have the same useEffect
hook which initializes the HMSSDK
. After initialization and adding listeners it creates an HMSConfig
object with AUTH_TOKEN
and USERNAME
and passes the created config object to join
method of HMSSDK
instance.
Render Video in a Tile
We are taking use of PeerTrackNode
class to render video and avatar of peers. Let's take a look at interface for PeerTrackNode
class -
interface PeerTrackNode { id: string; // Unique ID for each peer and track combination peer: HMSPeer; // `HMSPeer` object of peer track?: HMSTrack; // `HMSTrack` object of video track of the peer }
To display a video track, you can simply get the trackId
of the Video Tracks in PeerTrackNode
object and pass it to HmsView
component. If track
in PeerTrackNode
object is undefined or null then you can render avatar or name initials of the peer from the peer.name
property in PeerTrackNode
.
Check the Render Video guide for more information.
// `_renderItem` function returns a Tile UI for each item which is `PeerTrackNode` object const _renderItem = ({ item }) => { const { peer, track } = item; return ( <View style={{ height: 300, margin: 8, borderRadius: 20, overflow: 'hidden', backgroundColor: '#A0C3D2' }}> {/* Checking if we have "HmsView" component, valid trackId and "track is not muted" */} {HmsView && track && track.trackId && !track.isMute() ? ( // To Render Peer Live Videos, We can use HMSView // For more info about its props and usage, Check out {@link https://www.100ms.live/docs/react-native/v2/features/render-video | Render Video} <HmsView trackId={track.trackId} mirror={peer.isLocal} style={{ width: '100%', height: '100%' }} /> ) : ( // Render Peer name Initials <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <View style={{ width: 100, height: 100, borderRadius: 50, alignItems: 'center', justifyContent: 'center', backgroundColor: '#FD8A8A' }}> <Text style={{ textAlign: 'center', fontSize: 28, fontWeight: 'bold', textTransform: 'uppercase' }}> {peer.name .split(' ') .map((item) => item[0]) .join('')} </Text> </View> </View> )} </View> ); };
In the above code snippet, we have _renderItem
function which returns Tile UI. Inside Tile UI, we are rendering HmsView
if we have valid track otherwise an Avatar with Peer's Name Initials.
Render Video Tiles for Peers
You can create a list of PeerTrackNode
objects (mentioned in Render Video in a Tile step) for each Peer and Track combination and then use this list to render Tiles for all the PeerTrackNodes
objects. We recommend using FlatList component to render multiple Tiles. This ensures uniques Tiles are created for each trackId
.
const [peerTrackNodes, setPeerTrackNodes] = useState([]); // Use this state to render Peer Tiles ... const _keyExtractor = (item) => item.id; // `_renderItem` function returns a Tile UI for each item which is `PeerTrackNode` object const _renderItem = ({ item }) => { ... }; ... // Rendering list of Peers <FlatList centerContent={true} data={peerTrackNodes} showsVerticalScrollIndicator={false} keyExtractor={_keyExtractor} renderItem={_renderItem} contentContainerStyle={{ paddingBottom: 120, flexGrow: Platform.OS === 'android' ? 1 : undefined, justifyContent: Platform.OS === 'android' ? 'center' : undefined, }} />
In the above code snippet, we are rendering FlatList
for a list of PeerTrackNode
objects.
Leaving the Room
You can leave the meeting once you are done with it. To leave the meeting you can call the leave
method on HMSSDK
instance.
You can learn more about leave the room in Leave Room docs.
const handleRoomLeave = async () => { try { const hmsInstance = hmsInstanceRef.current; if (!hmsInstance) { return Promise.reject('HMSSDK instance is null'); } // Removing all registered listeners hmsInstance.removeAllListeners(); /** * Leave Room. For more info, Check out {@link https://www.100ms.live/docs/react-native/v2/features/leave | Leave Room} */ const leaveResult = await hmsInstance.leave(); console.log('Leave Success: ', leaveResult); /** * Free/Release Resources. For more info, Check out {@link https://www.100ms.live/docs/react-native/v2/features/release-resources | Release Resources} */ const destroyResult = await hmsInstance.destroy(); console.log('Destroy Success: ', destroyResult); // Removing HMSSDK instance hmsInstanceRef.current = null; } catch (error) { console.log('Leave or Destroy Error: ', error); } }; ... const handleRoomEnd = () => { leaveRoom(); navigate('HomeScreen'); }; ... <TouchableHighlight onPress={handleRoomEnd} underlayColor='#6e2028' style={{ position: 'absolute', bottom: 40, alignSelf: 'center', backgroundColor: '#CC525F', width: 60, height: 60, borderRadius: 30, alignItems: 'center', justifyContent: 'center', }} > <Text style={{ textAlign: 'center', color: '#ffffff', fontWeight: 'bold' }}>Leave Room</Text> </TouchableHighlight>
In the above code snippet, first we have handleRoomLeave
function which calls leave
and destroy
methods. Then we have implementation of "Leave Room" button UI in Room meeting screen which eventually calls handleRoomLeave
function.
Test App
You can refer to the Test the App section to test your app on Android or iOS Device or Simulators.
Next Steps
We have multiple example apps to get you started with 100ms React Native SDK -
Basic Example
For a Basic Sample Reference, check out the React Native Quickstart Sample App on GitHub.
Full-featured Example
You can also check out the fully featured Example App implementation in the 100ms React Native SDK GitHub repository showcasing multiple features provided by 100ms.
Check Deployed Sample Apps
You can download and check out the 100ms React Native deployed apps -
🤖 Download the Sample Android App here
📲 Download the Sample iOS App here