# or The [Zoom Video SDK](/docs/video-sdk/) lets you add real-time audio and video to your application easily, backed by the powerful Zoom technology users love. Let's look at how to integrate the Zoom Video SDK into your react-native app, by building a simple video conferencing application. Prerequisites: - [Environment setup](https://reactnative.dev/docs/environment-setup) for React Native - Node (LTS) & Yarn - A Zoom [Video SDK Account](/docs/video-sdk/get-credentials/) ## Step 1: Scaffold the application To scaffold our application we'll be using the [Expo](https://expo.dev/) Typescript template. Open a terminal and execute: ```bash $ yarn create expo zoom-video-sdk --template ``` Select `Blank(TypeScript)` when asked to "Choose a template". This will create a new folder called `zoom-video-sdk` with the following structure: ```bash . ├── App.tsx ├── app.json ├── assets ├── babel.config.js ├── node_modules ├── package.json ├── tsconfig.json └── yarn.lock ``` ## Step 2: Configuring the project **Install the dependencies** We will install the Zoom Video SDK and `react-native-pure-jwt`. You can use the following command for installation: ```bash $ yarn add @zoom/react-native-videosdk react-native-pure-jwt ``` We're using `v1.10.10` of the Zoom Video SDK, you can find the latest version [here](https://www.npmjs.com/package/@zoom/react-native-videosdk). **Add permissions** Add the following to your `app.json`. If you're not using Expo, you'll have to add these to your native files manually. ```json { "expo": { "android": { "permissions": ["CAMERA", "RECORD_AUDIO"] }, "ios": { "infoPlist": { "NSCameraUsageDescription": "Text", "NSMicrophoneUsageDescription": "Text" } } } } ``` **Install Pods** (iOS only) If you're building for iOS, you'll also need to install cocoapods. Open the Podfile in `/ios/Podfile` and add the following line to the `post_install` block: ```bash ... post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings["ONLY_ACTIVE_ARCH"] = "NO" end end ... ``` This is required to install the pods for all architectures. To install the pods, run the following command: ```bash $ npx pod-install ``` **Config file** Finally, let's create a `config.ts` in the root of our project and add some session details. We'll also add the Zoom App Key and Secret to generate a JWT. You can find these in the Zoom Marketplace in the "Develop" page under the "SDK credentials" section. ```js export const config = { sessionName: "DemoSession", roleType: "1", sessionPassword: "", displayName: "Ekaansh", sessionIdleTimeoutMins: 10, }; export const ZOOM_APP_KEY = ""; export const ZOOM_APP_SECRET = ""; ``` > **Disclaimer**: _To demo the app, we'll be using the Zoom Video SDK credentials to generate a JWT from within the Application. In production use-cases do not store your app key and secret inside your app. This is not a safe operation, instead use a server to generate the tokens and request them in your app instead._ ## Step 3: Building the app We can clear out the contents of the `App.tsx` file and start fresh. Add in the required imports for the project. We'll define `generateJwt`, `usePermission` hook and `styles` later. We'll also import the `config` we defined before. ```jsx import { useRef, useState } from "react"; import { EmitterSubscription, SafeAreaView, Text, View } from "react-native"; import { EventType, VideoAspect, ZoomVideoSdkProvider, ZoomVideoSdkUser, ZoomView, useZoom, } from "@zoom/react-native-videosdk"; import generateJwt from "./utils/jwt"; import Button, { usePermission } from "./utils/lib"; import { styles } from "./utils/styles"; import { config } from "./config"; ``` Let's create a root component `App`. We'll call the `usePermission` hook, it'll request for camera and microphone permissions on Android when the component is mounted. We'll wrap the return with the `ZoomVideoSdkProvider`, this allows us to use the `useZoom` hook down the component tree. We'll add in a `Call` component, let's define it next. ```jsx export default function App() { usePermission(); return ( ); } ``` ### The Call component The `Call` component will contain the videocall logic. We can access the Video SDK methods using the `useZoom` hook. We'll create a ref `listeners` to store an array of event subscriptions. We'll also define state variables - `users`: to keep track of the users in the call, `isInSession`: to check if we've joined the call, `isAudioMuted`: for audio mute status and `isVideoMuted`: for video mute status. ```jsx const Call = () => { const zoom = useZoom(); const listeners = useRef([]); const [users, setUsersInSession] = useState([]); const [isInSession, setIsInSession] = useState(false); const [isAudioMuted, setIsAudioMuted] = useState(true); const [isVideoMuted, setIsVideoMuted] = useState(true); ``` Let's define a function `join` to join a video call session. First, we'll access a JWT using our `generateJwt` function, in production this can be replaced with a network call to your backend. ```jsx const join = async () => { /* Disclaimer: JWT should be generated from your server */ const token = await generateJwt(config.sessionName, config.roleType); ``` ### Event listeners The Video SDK triggers events for various actions like user joining, leaving, video status change, etc. We can listen to these events using the `addListener` method. **Session Join** The `onSessionJoin` event is triggered when the user successfully joins a session. We can use this event to access the other users in the session and set the `isInSession` state to `true`. We'll append the returned subscription `sessionJoin` to the listeners ref to access later. ```jsx const sessionJoin = zoom.addListener(EventType.onSessionJoin, async () => { const mySelf = new ZoomVideoSdkUser(await zoom.session.getMySelf()); const remoteUsers = await zoom.session.getRemoteUsers(); setUsersInSession([mySelf, ...remoteUsers]); setIsInSession(true); }); listeners.current.push(sessionJoin); ``` **User Join & Leave** The `onUserJoin` and `onUserLeave` event is fired when a new remote user joins/leaves the session, we can use this to update the `users` state to keep track of the users in the session. We'll use this to render the user videos. ```jsx const userJoin = zoom.addListener(EventType.onUserJoin, async (event) => { const { remoteUsers } = event; const mySelf = await zoom.session.getMySelf(); const remote = remoteUsers.map((user) => new ZoomVideoSdkUser(user)); setUsersInSession([mySelf, ...remote]); }); listeners.current.push(userJoin); const userLeave = zoom.addListener(EventType.onUserLeave, async (event) => { const { remoteUsers } = event; const mySelf = await zoom.session.getMySelf(); const remote = remoteUsers.map((user) => new ZoomVideoSdkUser(user)); setUsersInSession([mySelf, ...remote]); }); listeners.current.push(userLeave); ``` **Audio & Video Mute** The `onUserVideoStatusChanged` and `onUserAudioStatusChanged` events are fired when any user mutes/unmutes their audio/video. We'll use these to update the local user's mute state with `setIsVideoMuted` and `setIsAudioMuted`. ```jsx const userVideo = zoom.addListener( EventType.onUserVideoStatusChanged, async (event) => { const { changedUsers } = event; const mySelf = new ZoomVideoSdkUser(await zoom.session.getMySelf()); changedUsers.find((user) => user.userId === mySelf.userId) && mySelf.videoStatus.isOn().then((on) => setIsVideoMuted(!on)); }, ); listeners.current.push(userVideo); const userAudio = zoom.addListener( EventType.onUserAudioStatusChanged, async (event) => { const { changedUsers } = event; const mySelf = new ZoomVideoSdkUser(await zoom.session.getMySelf()); changedUsers.find((user) => user.userId === mySelf.userId) && mySelf.audioStatus .isMuted() .then((muted) => setIsAudioMuted(muted)); }, ); listeners.current.push(userAudio); ``` **Session Leave** The `onSessionLeave` event is fired when we leave a session, we'll use this event to update the `IsInSession` state to `false`, setting the `users` state to an empty array and removing the event subscription. ```jsx const sessionLeave = zoom.addListener(EventType.onSessionLeave, () => { setIsInSession(false); setUsersInSession([]); sessionLeave.remove(); }); ``` After setting up all the listeners we can call the `joinSession` method to join the session. We'll pass in the `sessionName`, `sessionPassword`, `userName`, and `sessionIdleTimeoutMins` from the config. We'll pass in the `token` we generated along with `audioOptions` & `videoOptions`. ```jsx await zoom .joinSession({ sessionName: "TestOne", sessionPassword: "", userName: "test", sessionIdleTimeoutMins: 10, token: token, audioOptions: { connect: true, mute: true, autoAdjustSpeakerVolume: false }, videoOptions: { localVideoOn: true }, }) .catch((e) => { console.log(e); }); }; ``` ### Leaving the session To leave the call we'll define the `leaveSession` function, it will call the `leaveSession` method on the `zoom` object to leave the session. We're passing in `false` to leave the session, you can pass in `true` instead to end the session. We'll update the `isInSessison` state to `false` and remove all the listener subscriptions, and set the ref to an empty array. ```jsx const leaveSession = () => { zoom.leaveSession(false); setIsInSession(false); listeners.current.forEach((listener) => listener.remove()); listeners.current = []; }; ``` ### Rendering the UI The `Call` component will render conditionally based on the `isInSession` state. If we're in a session, we'll render the videos for each user by mapping over the `users` state array and passing in their `userId` to the `ZoomView` component. We'll render a `Button` to leave the session. We'll also render `MuteButtons` passing in the `isAudioMuted` and `isVideoMuted` state, we'll define this component next. ```jsx return isInSession ? ( {users.map((user) => ( ))} ) : ( ``` If a session isn't active, we'll display some text and render a button to call the `join` function. ```jsx Zoom Video SDK React Native Quickstart ); }; ``` ### MuteButtons component The `MuteButtons` component contains the logic to mute/unmute audio and video. We define the `onPressAudio` function that will call the `muteAudio` and `unmuteAudio` methods based on the user's audio state. We can access these using the `getMySelf` method on the `session` object. ```jsx const MuteButtons = ({ isAudioMuted, isVideoMuted }: { isAudioMuted: boolean; isVideoMuted: boolean }) => { const zoom = useZoom(); const onPressAudio = async () => { const mySelf = await zoom.session.getMySelf(); const muted = await mySelf.audioStatus.isMuted(); muted ? await zoom.audioHelper.unmuteAudio(mySelf.userId) : await zoom.audioHelper.muteAudio(mySelf.userId); }; ``` We'll do the same thing for the `onPressVideo` function as well. ```jsx const onPressVideo = async () => { const mySelf = await zoom.session.getMySelf(); const videoOn = await mySelf.videoStatus.isOn(); videoOn ? await zoom.videoHelper.stopVideo() : await zoom.videoHelper.startVideo(); }; ``` Finally, we can render out two `Button` components that call these functions `onPress`. ```jsx return ( ); }; ``` That wraps up all the code for the `App.tsx` file. Next, let's cover the permissions hook, jwt generation code and styling to complete our application. ### Permissions Here's the code for the `usePermission` hook we imported earlier, we'll save this as `utils/lib.ts`. ```jsx export async function requestCameraAndAudioPermission() { try { const granted = await PermissionsAndroid.requestMultiple([ PermissionsAndroid.PERMISSIONS.CAMERA, PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, ]); if ( granted["android.permission.RECORD_AUDIO"] === PermissionsAndroid.RESULTS.GRANTED && granted["android.permission.CAMERA"] === PermissionsAndroid.RESULTS.GRANTED ) { console.log("You can use the cameras & mic"); } else { console.log("Permission denied"); } } catch (err) { console.warn(err); } } export const usePermission = () => { useEffect(() => { if (Platform.OS === "android") { requestCameraAndAudioPermission(); } }, []); }; ``` ### JWT generation Like we mentioned before, in production you should generate the JWT from your server. Here's a sample implementation of the `generateJwt` function that we're using for this demo in `utils/jwt.ts`. ```js import { sign } from 'react-native-pure-jwt'; import { ZOOM_APP_KEY, ZOOM_APP_SECRET } from '../config'; function makeId(length: number) { var result = ''; var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; var charactersLength = characters.length; for (var i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; } export default async function generateJwt( sessionName: string, roleType: string ) { try { const token = await sign( { app_key: ZOOM_APP_KEY, version: 1, user_identity: makeId(10), iat: new Date().getTime(), exp: new Date(Date.now() + 23 * 3600 * 1000).getTime(), tpc: sessionName, role_type: parseInt(roleType, 10), cloud_recording_option: 1, }, ZOOM_APP_SECRET, { alg: 'HS256', } ); return token; } catch (e) { console.log(e); return ''; } } ``` > Note: Do not use this in a production application. ### Styling For completeness, here's the `styles` object we're using in the `App.tsx` file. ```js import { StyleSheet } from "react-native"; export const styles = StyleSheet.create({ safe: { width: "90%", alignSelf: "center", margin: 16, flex: 1, justifyContent: "center", }, container: { width: "100%", alignSelf: "center", height: "100%", flex: 1, justifyContent: "center", }, spacer: { height: 16, width: 8, }, heading: { fontSize: 24, fontWeight: "bold", textAlign: "center", }, button: { alignItems: "center", justifyContent: "center", paddingVertical: 12, paddingHorizontal: 32, borderRadius: 4, elevation: 3, }, buttonHolder: { flexDirection: "row", justifyContent: "center", margin: 8, }, text: { fontSize: 16, lineHeight: 21, fontWeight: "bold", letterSpacing: 0.25, color: "white", }, }); ``` ## Step 4: Running the app You can run the app using the following command: ```bash npx expo run:ios # or npx expo run:android ``` This will start the Expo development server and open the app in the iOS/Android simulator. > Note: the iOS simulator will not be able to access the camera, you'll see an empty video view. Please use a physical device to demo the video functionality. ## Conclusion Woo-hoo! You've successfully integrated video and audio conferencing into your React Native (Expo) application. This is just the beginning of what you can do with the Video SDK! You can add other features like screen sharing, chat, cloud recording, and live transcriptions. More information is available under the _Add Features_ section in the [Zoom Video SDK for React Native](/docs/video-sdk/react-native/) documentation. --- _For further community discussion and insight from Zoom Developer Advocates and other developers, please check out the [Zoom Developer Forum](https://devforum.zoom.us/). For prioritized assistance and troubleshooting, take advantage of [Premier Developer Support](https://explore.zoom.us/en/support-plans/developer/) and [submit a ticket](https://support.zoom.com/hc/en/new-request?id=new_request&sys_id=7a69170f87cee15089a37408dabb3591)._