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.

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:

  1. Create a Sample App β€” Instructions to create a React Native app quickly with a complete Code Sample.
  2. 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

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.

  1. Open your Terminal and navigate to directory/folder where you would like to create your app.

  2. Run the below command to create React Native app: ​

npx react-native init HMSSampleApp --version 0.68.5 --npm && cd ./HMSSampleApp
  1. Once the app is created, open it in VS code.

  2. 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 -

  1. Navigate to your 100ms dashboard or create an account if you don't have one.
  2. Use the Video Conferencing Starter Kit to create a room with a default template assigned to it to test this app quickly.
  3. Go to the Rooms page in your dashboard, click on the Room Id of the room you created above, and click on the Join Room button on the top right.
  4. 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
  1. 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
  1. 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.

  1. Install pods ​
cd ios && pod install && cd ../
  1. Build the App ​
npx react-native run-ios --simulator="iPhone 14"
  1. 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.

HMSSDK Integration Lifecycle

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, and
  • HMSUpdateListenerActions.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.

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


Have a suggestion? Recommend changes ->

Was this helpful?

1234