# Quickstart (/en/realtime-media/interactive-live-streaming/quickstart/react-native)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

This page provides a step-by-step guide on how to create a basic Interactive Live Streaming app using the Agora Video SDK.

    ## Understand the tech [#understand-the-tech-7]

    To start a Interactive Live Streaming session, implement the following steps in your app:

    * **Initialize the Agora Engine**: Before calling other APIs, create and initialize an Agora Engine instance.

    * **Join a channel**: Call methods to create and join a channel.

      * **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel. Hosts can also subscribe to streams from other hosts.

      * **Join as audience**: Audience members can only subscribe to streams published by hosts.

    * **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

    ![Streaming workflow](https://assets-docs.agora.io/images/video-sdk/get-started-ils-bs.svg)

    ## Prerequisites [#prerequisites-7]

    * React Native 0.60 or later. For more information, see [Get Started with React Native](https://reactnative.dev/docs/environment-setup).
    * Node 16 or later
      For your target platform, prepare the following:

    <CodeBlockTabs defaultValue="android">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="android">
          Android
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="ios">
          iOS
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="android">
        * A machine running macOS, Windows, or Linux
        * [Java Development Kit (JDK) 11](https://openjdk.org/projects/jdk/11/) or later
        * Latest version of Android Studio
        * A physical or virtual mobile device running Android 5.0 or later
      </CodeBlockTab>

      <CodeBlockTab value="ios">
        * A machine running macOS
        * Xcode 10 or later
        * CocoaPods
        * A physical or virtual mobile device running iOS 9.0 or later.
          If you use React Native 0.63 or later, ensure your iOS version is 10.0 or later.
      </CodeBlockTab>
    </CodeBlockTabs>

    * A camera and a microphone

    * A valid Agora account and project. Please refer to [Agora account management](manage-agora-account.md) for details.

    ## Set up your project [#set-up-your-project-7]

    This section shows you how to set up your React Native project and install the Agora Video SDK.

    ### Create a project [#create-a-project]

    To create a new application using [React Native Community CLI](https://github.com/react-native-community/cli), see [Get Started Without a Framework](https://reactnative.dev/docs/getting-started-without-a-framework).

    ### Install the SDK [#install-the-sdk-7]

    Download and add the Agora Video SDK to your React Native project. Choose either of the following methods:

    **npm**
    In your project folder, execute the following command:

    ```bash
    npm i --save react-native-agora
    ```

    **yarn**
    In your project folder, execute the following commands:

    ```bash
    # Install yarn
    npm install -g yarn

    # Use yarn to download Agora React Native SDK
    yarn add react-native-agora
    ```

    <CalloutContainer type="info">
      <CalloutDescription>
        React Native 0.60.0 and later support automatic linking of native modules. Manual linking is not recommended. See [Autolinking](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md) for details
      </CalloutDescription>
    </CalloutContainer>

    ## Implement Interactive Live Streaming [#implement-interactive-live-streaming-7]

    This section guides you through the implementation of basic real-time audio and video interaction in your app.

    The following figure illustrates the essential steps:

    <Accordions>
      <Accordion title="Quick start sequence">
        ![Quick start sequence](https://assets-docs.agora.io/images/video-sdk/quick-start-sequence.svg)
      </Accordion>
    </Accordions>

    This guide includes [complete sample code](#complete-sample-code) that demonstrates implementing basic real-time interaction. To understand the core API calls in the sample code, review the following implementation steps and use the code in your `App.tsx` file.

    ### Import dependencies [#import-dependencies-1]

    Add the required Agora dependencies to your app.

    ```tsx
    import {
      createAgoraRtcEngine,
      ChannelProfileType,
      ClientRoleType,
      AudienceLatencyLevelType,
      IRtcEngine,
      RtcSurfaceView,
      RtcConnection,
      IRtcEngineEventHandler,
    } from 'react-native-agora';
    ```

    ### Initialize the engine [#initialize-the-engine-6]

    For real-time communication, call `createAgoraRtcEngine` to create an `RtcEngine` instance, and then `initialize` the engine using `RtcEngineContext` to specify the [App ID](manage-agora-account.md).

    ```tsx
    const appId = '<-- Insert app ID -->';

    const setupVideoSDKEngine = async () => {
      if (Platform.OS === 'android') { await getPermission(); }
      agoraEngineRef.current = createAgoraRtcEngine();
      const agoraEngine = agoraEngineRef.current;
      await agoraEngine.initialize({ appId: appId });
    };
    ```

    ### Join a channel [#join-a-channel-7]

    To join a channel, call `joinChannel` with the following parameters:

    * **Channel name**: The name of the channel to join. Clients that pass the same channel name join the same channel. If a channel with the specified name does not exist, it is created when the first user joins.

    * **Authentication token**: A dynamic key that authenticates a user when the client joins a channel. In a production environment, you obtain a token from a [token server](build/authenticate-users/deploy-token-server.mdx) in your security infrastructure. For the purpose of this guide [Generate a temporary token](manage-agora-account.md).

    * **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.

    * **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.

    For Interactive Live Streaming, set the `channelProfile` to `ChannelProfileLiveBroadcasting`, the `clientRoleType` to `ClientRoleBroadcaster` (host) or `ClientRoleAudience`, and the `audienceLatencyLevel` to `audienceLatencyLevelUltraLowLatency`.

    ```tsx
    const token = '<-- Insert token -->';
    const channelName = '<-- Insert channel name -->';
    const localUid = 0; // Local user UID, no need to modify

    // Define the join method called after clicking the join channel button
    const join = async () => {
      if (isJoined) {
        return;
      }
      if (isHost) {
        // Join the channel as a broadcaster
        agoraEngineRef.current?.joinChannel(token, channelName, localUid, {
          // Set channel profile to live broadcast
          channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
          // Set user role to broadcaster
          clientRoleType: ClientRoleType.ClientRoleBroadcaster,
          // Publish audio collected by the microphone
          publishMicrophoneTrack: true,
          // Publish video collected by the camera
          publishCameraTrack: true,
          // Automatically subscribe to all audio streams
          autoSubscribeAudio: true,
          // Automatically subscribe to all video streams
          autoSubscribeVideo: true,
        });
      } else {
        // Join the channel as an audience
        agoraEngineRef.current?.joinChannel(token, channelName, localUid, {
          // Set channel profile to live broadcast
          channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
          // Set user role to audience
          clientRoleType: ClientRoleType.ClientRoleAudience,
          // Do not publish audio collected by the microphone
          publishMicrophoneTrack: false,
          // Do not publish video collected by the camera
          publishCameraTrack: false,
          // Automatically subscribe to all audio streams
          autoSubscribeAudio: true,
          // Automatically subscribe to all video streams
          autoSubscribeVideo: true,',
          // Change the delay level of the audience to achieve ultra-fast live broadcast
          audienceLatencyLevel: AudienceLatencyLevelType.audienceLatencyLevelUltraLowLatency,
        });
      }
    };
    ```

    <CalloutContainer type="info">
      <CalloutDescription>
        Since the Agora Engine runs locally, reloading the app using the Metro bundler may cause the engine reference to be lost, preventing channel joining. To resolve this issue, restart the app.
      </CalloutDescription>
    </CalloutContainer>

    ### Subscribe to Video SDK events [#subscribe-to-video-sdk-events-5]

    The Video SDK provides an interface for subscribing to channel events. To use it, create an instance of `IRtcEngineEventHandler` and implement the event methods you want to handle. Call `registerEventHandler` to register your custom event handler.

    ```tsx
    const [remoteUid, setRsemoteUid] = useState(0); // Uid of the remote user

    const setupEventHandler = () => {
      eventHandler.current = {
        // Triggered when the local user successfully joins a channel
        onJoinChannelSuccess: () => {
          setMessage('Successfully joined channel: ' + channelName);
          setupLocalVideo();
          setIsJoined(true);
        },
        // Triggered when a remote user joins the channel
        onUserJoined: (_connection: RtcConnection, uid: number) => {
          setMessage('Remote user ' + uid + ' joined');
          setRemoteUid(uid);
        },
        // Triggered when a remote user leaves the channel
        onUserOffline: (_connection: RtcConnection, uid: number) => {
          setMessage('Remote user ' + uid + ' left the channel');
          setRemoteUid(uid);
        },
      };
      // Register the event handler
      agoraEngineRef.current?.registerEventHandler(eventHandler.current);
    };
    ```

    <CalloutContainer type="info">
      <CalloutDescription>
        To ensure that you receive all Video SDK events, register the event handler before joining a channel.
      </CalloutDescription>
    </CalloutContainer>

    ### Enable the video module [#enable-the-video-module-5]

    Call `enableVideo` to activate the video module, and use `startPreview` to enable the local video preview.

    ```tsx
    const setupLocalVideo = () => {
      agoraEngineRef.current?.enableVideo();
      agoraEngineRef.current?.startPreview();
    };
    ```

    ### Display the local video [#display-the-local-video-7]

    To display the local user's video stream, use the `RtcSurfaceView` component. Set the `uid` to `0` and `style` the video container.

    ```tsx
    <React.Fragment key={0}>
      <RtcSurfaceView canvas={{ uid: 0 }} style={ width: '90%', height: 200 } />
    </React.Fragment>
    ```

    ### Display remote video [#display-remote-video-7]

    To display the remote user's video, use the `RtcSurfaceView` component. Pass the `remoteUid` of the remote user to the component and set the desired `style` for the video container.

    ```tsx
    <React.Fragment key={remoteUid}>
      <RtcSurfaceView
        canvas={{ uid: remoteUid }}
        style={ width: '90%', height: 200 }
      />
    </React.Fragment>
    ```

    ### Handle permissions [#handle-permissions-5]

    To access the camera and microphone, ensure that the user grants the necessary permissions when the app starts.

    <CodeBlockTabs defaultValue="android">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="android">
          Android
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="ios">
          iOS
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="android">
        On Android devices, pop up a prompt box to obtain permission to use the camera and microphone.

        ```tsx
        // Import components related to Android device permissions
        import {PermissionsAndroid, Platform} from 'react-native';

        const getPermission = async () => {
        if (Platform.OS === 'android') {
          await PermissionsAndroid.requestMultiple([
            PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
            PermissionsAndroid.PERMISSIONS.CAMERA,
          ]);
        }
        };
        ```
      </CodeBlockTab>

      <CodeBlockTab value="ios">
        In Xcode, open the `info.plist` file and add the following content to the list on the right to obtain the corresponding device permissions:

        | Key                                    | Type   | Value                                                                                       |
        | -------------------------------------- | ------ | ------------------------------------------------------------------------------------------- |
        | Privacy - Microphone Usage Description | String | The purpose of using the microphone, for example, for a call or live interactive streaming. |
        | Privacy - Camera Usage Description     | String | The purpose of using the camera, for example, for a call or live interactive streaming.     |
      </CodeBlockTab>
    </CodeBlockTabs>

    ### Leave the channel [#leave-the-channel-2]

    To exit a Interactive Live Streaming channel, call `leaveChannel`.

    ```tsx
    const leave = () => {
      // Leave the channel
      agoraEngineRef.current?.leaveChannel();
      setIsJoined(false);
    };
    ```

    ### Clean up resources [#clean-up-resources]

    Before closing your app, unregister the event handler and call `release` to free up resources.

    ```tsx
    const cleanupAgoraEngine = () => {
      return () => {
        agoraEngineRef.current?.unregisterEventHandler(eventHandler.current!);
        agoraEngineRef.current?.release();
      };
    };
    ```

    <CalloutContainer type="info">
      <CalloutDescription>
        After calling `release,` methods and callbacks of the SDK are no longer available. To use the SDK functions again, create a new engine instance.
      </CalloutDescription>
    </CalloutContainer>

    ### Complete sample code [#complete-sample-code-7]

    A complete code sample demonstrating the basic process of real-time interaction is provided for your reference. To use the sample code, copy the code into `ProjectName/App.tsx` to quickly implement the basic functionality.

    <Accordions>
      <Accordion title="Complete sample code for real-time Interactive Live Streaming">
        ```tsx
        // Import React Hooks
        import React, { useRef, useState, useEffect } from 'react';
        // Import user interface elements
        import {
          SafeAreaView,
          ScrollView,
          StyleSheet,
          Text,
          View,
          Switch,
        } from 'react-native';
        // Import components related to obtaining Android device permissions
        import { PermissionsAndroid, Platform } from 'react-native';
        // Import Agora SDK
        import {
          createAgoraRtcEngine,
          ChannelProfileType,
          ClientRoleType,
          IRtcEngine,
          RtcSurfaceView,
          RtcConnection,
          IRtcEngineEventHandler,
          VideoSourceType,
        } from 'react-native-agora';

        // Define basic information
        const appId = '<-- Insert App ID -->';
        const token = '<-- Insert Token -->';
        const channelName = '<-- Insert Channel Name -->';
        const localUid = 0; // Local user Uid, no need to modify

        const App = () => {
          const agoraEngineRef = useRef<IRtcEngine>(); // IRtcEngine instance
          const [isJoined, setIsJoined] = useState(false); // Whether the local user has joined the channel
          const [isHost, setIsHost] = useState(true); // User role
          const [remoteUid, setRemoteUid] = useState(0); // Uid of the remote user
          const [message, setMessage] = useState(''); // User prompt message
          const eventHandler = useRef<IRtcEngineEventHandler>(); // Implement callback functions

          useEffect(() => {
            const init = async () => {
              await setupVideoSDKEngine();
              setupEventHandler();
            };
            init();
            return () => {
              cleanupAgoraEngine(); // Ensure this is synchronous
            };
          }, []); // Empty dependency array ensures it runs only once

          const setupEventHandler = () => {
            eventHandler.current = {
              onJoinChannelSuccess: () => {
                setMessage('Successfully joined channel: ' + channelName);
                setupLocalVideo();
                setIsJoined(true);
              },
              onUserJoined: (_connection: RtcConnection, uid: number) => {
                setMessage('Remote user ' + uid + ' joined');
                setRemoteUid(uid);
              },
              onUserOffline: (_connection: RtcConnection, uid: number) => {
                setMessage('Remote user ' + uid + ' left the channel');
                setRemoteUid(uid);
              },
            };
            agoraEngineRef.current?.registerEventHandler(eventHandler.current);
          };

          const setupVideoSDKEngine = async () => {
            try {
              if (Platform.OS === 'android') { await getPermission(); }
              agoraEngineRef.current = createAgoraRtcEngine();
              const agoraEngine = agoraEngineRef.current;
              await agoraEngine.initialize({ appId: appId });
            } catch (e) {
              console.error(e);
            }
          };

          const setupLocalVideo = () => {
            agoraEngineRef.current?.enableVideo();
            agoraEngineRef.current?.startPreview();
          };

          // Define the join method called after clicking the join channel button
          const join = async () => {
            if (isJoined) {
              return;
            }
            try {
              if (isHost) {
                // Join the channel as a broadcaster
                agoraEngineRef.current?.joinChannel(token, channelName, localUid, {
                  // Set channel profile to live broadcast
                  channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
                  // Set user role to broadcaster
                  clientRoleType: ClientRoleType.ClientRoleBroadcaster,
                  // Publish audio collected by the microphone
                  publishMicrophoneTrack: true,
                  // Publish video collected by the camera
                  publishCameraTrack: true,
                  // Automatically subscribe to all audio streams
                  autoSubscribeAudio: true,
                  // Automatically subscribe to all video streams
                  autoSubscribeVideo: true,
                });
              } else {
                // Join the channel as an audience
                agoraEngineRef.current?.joinChannel(token, channelName, localUid, {
                  // Set channel profile to live broadcast
                  channelProfile: ChannelProfileType.ChannelProfileLiveBroadcasting,
                  // Set user role to audience
                  clientRoleType: ClientRoleType.ClientRoleAudience,
                  // Do not publish audio collected by the microphone
                  publishMicrophoneTrack: false,
                  // Do not publish video collected by the camera
                  publishCameraTrack: false,
                  // Automatically subscribe to all audio streams
                  autoSubscribeAudio: true,
                  // Automatically subscribe to all video streams
                  autoSubscribeVideo: true,',
                  // Change the delay level of the audience to achieve ultra-fast live broadcast
                  audienceLatencyLevel: AudienceLatencyLevelType.audienceLatencyLevelUltraLowLatency,
                });
              }
            } catch (e) {
              console.log(e);
            }
          };

          // Define the leave method called after clicking the leave channel button
          const leave = () => {
            try {
              // Call leaveChannel method to leave the channel
              agoraEngineRef.current?.leaveChannel();
              setRemoteUid(0);
              setIsJoined(false);
              showMessage('Left the channel');
            } catch (e) {
              console.log(e);
            }
          };

          const cleanupAgoraEngine = () => {
            return () => {
              agoraEngineRef.current?.unregisterEventHandler(eventHandler.current!);
              agoraEngineRef.current?.release();
            };
          };

          // Render user interface
          return (
            <SafeAreaView style={styles.main}>
              <Text style={styles.head}>Agora Video SDK Quickstart</Text>
              <View style={styles.btnContainer}>
                <Text onPress={join} style={styles.button}>
                  Join Channel
                </Text>
                <Text onPress={leave} style={styles.button}>
                  Leave Channel
                </Text>
              </View>
              <View style={styles.btnContainer}>
                <Text>Audience</Text>
                <Switch
                  onValueChange={switchValue => {
                    setIsHost(switchValue);
                    if (isJoined) {
                      leave();
                    }
                  }}
                  value={isHost}
                />
                <Text>Host</Text>
              </View>
              <ScrollView
                style={styles.scroll}
                contentContainerStyle={styles.scrollContainer}>
                {isJoined && isHost ? (
                  <React.Fragment key={localUid}>
                      <Text>Local user uid: {localUid}</Text>
                      <RtcSurfaceView canvas={{ uid: localUid, sourceType: VideoSourceType.VideoSourceCamera }} style={styles.videoView} />
                  </React.Fragment>
                ) : (
                  <Text>Join a channel</Text>
                )}
                {isJoined && remoteUid !== 0 ? (
                  <React.Fragment key={remoteUid}>
                    <Text>Remote user uid: {remoteUid}</Text>
                    <RtcSurfaceView canvas={{ uid: remoteUid, sourceType: VideoSourceType.VideoSourceCamera }} style={styles.videoView} />
                  </React.Fragment>
                ) : (
                  <Text>{isJoined && !isHost ? 'Waiting for remote user to join' : ''}</Text>
                )}
                <Text style={styles.info}>{message}</Text>
              </ScrollView>
            </SafeAreaView>
          );

          // Display information
          function showMessage(msg: string) {
            setMessage(msg);
          }
        };

        // Define user interface styles
        const styles = StyleSheet.create({
          button: {
            paddingHorizontal: 25,
            paddingVertical: 4,
            fontWeight: 'bold',
            color: '#ffffff',
            backgroundColor: '#0055cc',
            margin: 5,
          },
          main: { flex: 1, alignItems: 'center' },
          scroll: { flex: 1, backgroundColor: '#ddeeff', width: '100%' },
          scrollContainer: { alignItems: 'center' },
          videoView: { width: '90%', height: 200 },
          btnContainer: { flexDirection: 'row', justifyContent: 'center' },
          head: { fontSize: 20 },
          info: { backgroundColor: '#ffffe0', paddingHorizontal: 8, color: '#0000ff' },
        });

        const getPermission = async () => {
          if (Platform.OS === 'android') {
            await PermissionsAndroid.requestMultiple([
              PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
              PermissionsAndroid.PERMISSIONS.CAMERA,
            ]);
          }
        };

        export default App;
        ```
      </Accordion>
    </Accordions>

    ### Create a user interface [#create-a-user-interface-7]

    Design a user interface for your project based on your application use-case. A basic interface consists of two view frames to display local and remote videos. Add **Join channel** and **Leave channel** buttons to enable the user to join and leave a channel. To create such an interface, refer to the following code:

    **Sample code to create the user interface**

    ```tsx
    import React, { useState } from 'react';

    // Import user interface elements
    import {
      SafeAreaView,
      ScrollView,
      StyleSheet,
      Text,
      View,
      Switch,
    } from 'react-native';

    const localUid = 0; // Local user Uid, no need to modify

    const App = () => {
      const [isJoined, setIsJoined] = useState(false); // Whether the local user has joined the channel
      const [isHost, setIsHost] = useState(true); // User role
      const [remoteUid, setRemoteUid] = useState(0); // Uid of the remote user
      const [message, setMessage] = useState(''); // User prompt message

      // Render user interface
      return (
        <SafeAreaView style={styles.main}>
          <Text style={styles.head}>Agora Video SDK Quickstart</Text>
          <View style={styles.btnContainer}>
            <Text onPress={join} style={styles.button}>
              Join Channel
            </Text>
            <Text onPress={leave} style={styles.button}>
              Leave Channel
            </Text>
          </View>
          <View style={styles.btnContainer}>
            <Text>Audience</Text>
            <Switch
              onValueChange={switchValue => {
                setIsHost(switchValue);
                if (isJoined) {
                  leave();
                }
              }}
              value={isHost}
            />
            <Text>Host</Text>
          </View>
          <ScrollView
            style={styles.scroll}
            contentContainerStyle={styles.scrollContainer}>
            {isJoined && isHost ? (
              <React.Fragment key={localUid}>
                <Text>Local user uid: {localUid}</Text>
              </React.Fragment>
            ) : (
              <Text>Join a channel</Text>
            )}
            {isJoined && remoteUid !== 0 ? (
              <React.Fragment key={remoteUid}>
                <Text>Remote user uid: {remoteUid}</Text>
              </React.Fragment>
            ) : (
              <Text>{isJoined && !isHost ? 'Waiting for remote user to join' : ''}</Text>
            )}
            <Text style={styles.info}>{message}</Text>
          </ScrollView>
        </SafeAreaView>
      );
    };

    // Define user interface styles
    const styles = StyleSheet.create({
      button: {
        paddingHorizontal: 25,
        paddingVertical: 4,
        fontWeight: 'bold',
        color: '#ffffff',
        backgroundColor: '#0055cc',
        margin: 5,
      },
      main: { flex: 1, alignItems: 'center' },
      scroll: { flex: 1, backgroundColor: '#ddeeff', width: '100%' },
      scrollContainer: { alignItems: 'center' },
      videoView: { width: '90%', height: 200 },
      btnContainer: { flexDirection: 'row', justifyContent: 'center' },
      head: { fontSize: 20 },
      info: { backgroundColor: '#ffffe0', paddingHorizontal: 8, color: '#0000ff' },
    });

    export default App;
    ```

    ## Test the sample code [#test-the-sample-code-7]

    Take the following steps to test the sample code:

    1. Update the values for `appId`, `token`, and `channelName` in your code.

    2. Run the app.

       <CalloutContainer type="warning">
         <CalloutDescription>
           Some simulators may not support all the functions of this project. Best practice is to run the project on a physical device.
         </CalloutDescription>
       </CalloutContainer>

    <CodeBlockTabs defaultValue="android">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="android">
          Android
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="ios">
          iOS
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="android">
        To run the app on an Android device:

        3. Turn on the developer options of the Android device and connect the Android device to the computer through a USB cable.

        4. In the project root directory, execute `npx react-native run-android`.
      </CodeBlockTab>

      <CodeBlockTab value="ios">
        To run on the app on an iOS device:

        5. Open the `ProjectName/ios/ProjectName.xcworkspace` folder using Xcode.

        6. Connect the iOS device to your computer through a USB cable.

        7. In Xcode, click the **Build and Run** button.
      </CodeBlockTab>
    </CodeBlockTabs>

    <CalloutContainer type="info">
      <CalloutDescription>
        For detailed steps on running the app on a real Android or iOS device, please refer to [Running On Device](https://reactnative.dev/docs/running-on-device).
      </CalloutDescription>
    </CalloutContainer>

    8. Launch the app and click the **Join channel** button to join a channel.

    9. Invite a friend to install and run the app on a second device. Alternatively, use the [Web demo](https://webdemo.agora.io/basicVideoCall/index.html) to join the same channel. Once your friend joins the channel, you can see and hear each other.

    \:::

    ## Reference [#reference-7]

    This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

    * If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](build/optimize-quality-and-connection/cloud-proxy.mdx) to use Agora services normally.

    ### Next steps [#next-steps-7]

    After implementing the quickstart sample, read the following documents to learn more:

    * To ensure communication security in a test or production environment, best practice is to obtain and use a token from an authentication server. For details, see [Secure authentication with tokens](build/authenticate-users/use-tokens.mdx).

    ### Sample project [#sample-project-7]

    Agora provides open source sample projects on [GitHub](https://github.com/AgoraIO-Extensions/react-native-agora/blob/main/example/src/examples) for your reference. Download or view the [JoinChannelVideo](https://github.com/AgoraIO-Extensions/react-native-agora/blob/main/example/src/examples/basic/JoinChannelVideo) project for a more detailed example.

    ### API reference [#api-reference-7]

    * [`createAgoraRtcEngine`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_createagorartcengine)

    * [`initialize`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_initialize)

    * [`registerEventHandler`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_addhandler)

    * [`setChannelProfile`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_setchannelprofile)

    * [`joinChannel`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_joinchannel2)

    * [`leaveChannel`](https://api-ref.agora.io/en/video-sdk/react-native/4.x/API/class_irtcengine.html#api_irtcengine_leavechannel2)

    ### Frequently asked questions [#frequently-asked-questions-6]

    * [How can I fix black screen issues?](/en/api-reference/faq/quality/video_blank)

    * [Why can't I turn on the camera?](/en/api-reference/faq/quality/video_camera)

    * [How can I listen for audience joining or leaving a channel?](/en/api-reference/faq/integration/audience_event)

    * [How can I solve channel-related issues?](/en/api-reference/faq/integration/channel)

    * [How can I set the log file?](/en/api-reference/faq/integration/set_log_file)

    ### See also [#see-also-7]

    * [Error codes](/en/realtime-media/video/reference/error-codes)

    * [Connection status management](build/optimize-quality-and-connection/connection-status-management.mdx)

    
  
      
  
      
  
      
  
      
  
      
  
