# Quickstart (/en/realtime-media/video/get-started-sdk)

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

<_PlatformTabsGroup groupMode="structured" canonicalPlatform="web" platforms="[&#x22;android&#x22;,&#x22;ios&#x22;,&#x22;macos&#x22;,&#x22;web&#x22;,&#x22;windows&#x22;,&#x22;electron&#x22;,&#x22;flutter&#x22;,&#x22;react-native&#x22;,&#x22;javascript&#x22;,&#x22;unity&#x22;,&#x22;unreal&#x22;,&#x22;blueprint&#x22;]" showTabs="true">
  <_PlatformPanel platform="android">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />

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

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

    To start a Video Calling session, implement the following steps in your app:

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

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

    * **Send and receive audio and video**: All users can publish streams to the channel and subscribe to audio and video streams published by other users in the channel.

    ![Video calling workflow](https://assets-docs.agora.io/images/video-sdk/video-call.svg)

    ## Prerequisites [#prerequisites]

    * A camera and a microphone.
    * A valid Agora account and project. See [Agora account management](/en/realtime-media/video/manage-agora-account) for details.
    * [Android Studio](https://developer.android.com/studio) 4.2 or higher.
    * Android SDK API Level 21 or higher.
    * Two mobile devices running Android 5.0 or higher.

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

    <Tabs defaultValue="new">
      <TabsList>
        <TabsTrigger value="new">
          Create a new project
        </TabsTrigger>

        <TabsTrigger value="existing">
          Add to an existing project
        </TabsTrigger>
      </TabsList>

      <TabsContent value="new">
        1. Create a [new project](https://developer.android.com/studio/projects/create-project).

           1. Open Android Studio and select &#x2A;*File > New > New Project...**.
           2. Select **Phone and Tablet** > **Empty Activity** and click **Next**.
           3. Set the project name and storage path.
           4. Select **Java** or **Kotlin** as the language, and click **Finish** to create the Android project.

              <CalloutContainer type="warning">
                <CalloutTitle>
                  Note
                </CalloutTitle>

                <CalloutDescription>
                  After you create a project, Android Studio automatically starts gradle sync. Ensure that the synchronization is successful before proceeding to the next step.
                </CalloutDescription>
              </CalloutContainer>
      </TabsContent>

      <TabsContent value="existing">
        1. Add a new activity to your project.

           1. Open your project in Android Studio.
           2. Right-click on the `app/src/main/java/<your.package.name>` folder.
           3. Select **New → Activity → Empty Activity**.
           4. Enter an activity name and click **Finish**.
              This guide uses `MainActivity` as the activity name in the sample code. Replace it with your activity name where required.
      </TabsContent>
    </Tabs>

    2. Add a layout file for your activity.

       Set up two container elements in your activity to display local and remote video streams. Refer to [Create a user interface](#create-a-user-interface) to get a bare bones sample layout.

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

    Use either of the following methods to add Video SDK to your project.

    <Tabs defaultValue="maven">
      <TabsList>
        <TabsTrigger value="maven">
          Maven Central
        </TabsTrigger>

        <TabsTrigger value="manual">
          Manual download
        </TabsTrigger>
      </TabsList>

      <TabsContent value="maven">
        1. Open the `settings.gradle` file in the project's root directory and add the Maven Central dependency, if it doesn't already exist:

           ```groovy
           repositories {
              mavenCentral()
           }
           ```

           <CalloutContainer type="info">
             <CalloutDescription>
               If your Android project uses [dependencyResolutionManagement](https://docs.gradle.org/current/userguide/declaring_repositories.html#sub\:centralized-repository-declaration), the method of adding the Maven Central dependency may differ.
             </CalloutDescription>
           </CalloutContainer>

        2. To integrate the Video SDK into your Android project, add the following to the `dependencies` block in your project module `build.gradle` file:

           * Groovy `build.gradle`

             ```groovy
             implementation 'io.agora.rtc:full-sdk:x.y.z'
             ```

           * Kotlin `build.gradle.kts`

             ```kotlin
             implementation("io.agora.rtc:full-sdk:x.y.z")
             ```

           Replace `x.y.z` with the specific SDK version number, such as `4.5.0`.

           <CalloutContainer type="info">
             <CalloutDescription>
               To get the latest version number, check the [Release notes](/en/realtime-media/video/reference/release-notes). To integrate the Lite SDK, use `io.agora.rtc:lite-sdk` instead.
             </CalloutDescription>
           </CalloutContainer>

        3. Prevent code obfuscation

           Open the `/app/proguard-rules.pro` file and add the following lines to prevent the Video SDK code from being obfuscated:

           ```java
           -keep class io.agora.** { *; }
           -dontwarn io.agora.**
           ```
      </TabsContent>

      <TabsContent value="manual">
        1. Download the latest version of Video SDK from the [SDKs](/en/api-reference/sdks?platform=android) page and unzip it.

        2. Open the unzipped file and copy the following files or subfolders to your project path.

           | File or folder                       | Project path             |
           | :----------------------------------- | :----------------------- |
           | `agora-rtc-sdk.jar` file             | `/app/libs/`             |
           | `arm64-v8a` folder                   | `/app/src/main/jniLibs/` |
           | `armeabi-v7a` folder                 | `/app/src/main/jniLibs/` |
           | `x86` folder                         | `/app/src/main/jniLibs/` |
           | `x86_64` folder                      | `/app/src/main/jniLibs/` |
           | `high_level_api` in `include` folder | `/app/src/main/jniLibs/` |

        3. Select the file `/app/libs/agora-rtc-sdk.jar` in the left navigation bar of Android Studio project files, right-click, and select **add as a library** from the drop-down menu.

        4. Prevent code obfuscation

           Open the `/app/proguard-rules.pro` file and add the following lines to prevent the Video SDK code from being obfuscated:

           ```java
           -keep class io.agora.** { *; }
           -dontwarn io.agora.**
           ```
      </TabsContent>
    </Tabs>

    ## Implement Video Calling [#implement-video-calling]

    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">
        ![Video Calling 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 `MainActivity` file.

    ### Import Agora classes [#import-agora-classes]

    Import the relevant Agora classes and interfaces:

    <Tabs defaultValue="java">
      <TabsList>
        <TabsTrigger value="java">
          Java
        </TabsTrigger>

        <TabsTrigger value="kotlin">
          Kotlin
        </TabsTrigger>
      </TabsList>

      <TabsContent value="java">
        ```java
        import io.agora.rtc2.Constants;
        import io.agora.rtc2.IRtcEngineEventHandler;
        import io.agora.rtc2.RtcEngine;
        import io.agora.rtc2.RtcEngineConfig;
        import io.agora.rtc2.video.VideoCanvas;
        import io.agora.rtc2.ChannelMediaOptions;
        ```
      </TabsContent>

      <TabsContent value="kotlin">
        ```kotlin
        import io.agora.rtc2.Constants
        import io.agora.rtc2.IRtcEngineEventHandler
        import io.agora.rtc2.RtcEngine
        import io.agora.rtc2.RtcEngineConfig
        import io.agora.rtc2.video.VideoCanvas
        import io.agora.rtc2.ChannelMediaOptions
        ```
      </TabsContent>
    </Tabs>

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

    For real-time communication, initialize an `RtcEngine` instance and set up event handlers to manage user interactions within the channel. Use `RtcEngineConfig` to specify the application context, [App ID](/en/realtime-media/video/manage-agora-account#get-the-app-id), and custom [event handler](#subscribe-to-video-sdk-events), then call `RtcEngine.create(config)` to initialize the engine, enabling further channel operations. In your `MainActivity` file, add the following code:

    <Tabs defaultValue="java">
      <TabsList>
        <TabsTrigger value="java">
          Java
        </TabsTrigger>

        <TabsTrigger value="kotlin">
          Kotlin
        </TabsTrigger>
      </TabsList>

      <TabsContent value="java">
        ```java
        // Fill in the app ID from Agora Console
        private String myAppId = "<Your app ID>";
        private RtcEngine mRtcEngine;

        private void initializeAgoraVideoSDK() {
            try {
                RtcEngineConfig config = new RtcEngineConfig();
                config.mContext = getBaseContext();
                config.mAppId = myAppId;
                config.mEventHandler = mRtcEventHandler;
                mRtcEngine = RtcEngine.create(config);
            } catch (Exception e) {
                throw new RuntimeException("Error initializing RTC engine: " + e.getMessage());
            }
        }
        ```
      </TabsContent>

      <TabsContent value="kotlin">
        ```kotlin
        // Fill in the App ID obtained from the Agora Console
        private val myAppId = "<Your app ID>"
        private var mRtcEngine: RtcEngine? = null

        private fun initializeRtcEngine() {
            try {
                val config = RtcEngineConfig().apply {
                    mContext = applicationContext
                    mAppId = myAppId
                    mEventHandler = mRtcEventHandler
                }
                mRtcEngine = RtcEngine.create(config)
            } catch (e: Exception) {
                throw RuntimeException("Error initializing RTC engine: ${e.message}")
            }
        }
        ```
      </TabsContent>
    </Tabs>

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

    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](/en/realtime-media/video/build/authenticate-users/deploy-token-server) in your security infrastructure. For the purpose of this guide [Generate a temporary token](/en/realtime-media/video/manage-agora-account#generate-temporary-tokens).

    * **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 Video Calling, set the `channelProfile` to `CHANNEL_PROFILE_COMMUNICATION` and the `clientRoleType` to `CLIENT_ROLE_BROADCASTER`.

    <Tabs defaultValue="java">
      <TabsList>
        <TabsTrigger value="java">
          Java
        </TabsTrigger>

        <TabsTrigger value="kotlin">
          Kotlin
        </TabsTrigger>
      </TabsList>

      <TabsContent value="java">
        ```java
        // Fill in the channel name
        private String channelName = "<Your channel name>";
        // Fill in the temporary token generated from Agora Console
        private String token = "<Your token>";

        private void joinChannel() {
            ChannelMediaOptions options = new ChannelMediaOptions();
            options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
            options.channelProfile = Constants.CHANNEL_PROFILE_COMMUNICATION;
            options.publishCameraTrack = true;
            options.publishMicrophoneTrack = true;
            mRtcEngine.joinChannel(token, channelName, 0, options);
        }
        ```
      </TabsContent>

      <TabsContent value="kotlin">
        ```kotlin
        // Fill in the channel name
        private val channelName = "<Your channel name>"
        // Fill in the temporary token generated from Agora Console
        private val token = "<Your token>"

        private fun joinChannel() {
            val options = ChannelMediaOptions().apply {
                clientRoleType = Constants.CLIENT_ROLE_BROADCASTER
                channelProfile = Constants.CHANNEL_PROFILE_COMMUNICATION
                publishMicrophoneTrack = true
                publishCameraTrack = true
            }
            mRtcEngine.joinChannel(token, channelName, 0, options)
        }
        ```
      </TabsContent>
    </Tabs>

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

    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.

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

    <Tabs defaultValue="java">
      <TabsList>
        <TabsTrigger value="java">
          Java
        </TabsTrigger>

        <TabsTrigger value="kotlin">
          Kotlin
        </TabsTrigger>
      </TabsList>

      <TabsContent value="java">
        ```java
        private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
            // Triggered when the local user successfully joins the specified channel.
            @Override
            public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
                super.onJoinChannelSuccess(channel, uid, elapsed);
                showToast("Joined channel " + channel);
            }

            // Triggered when a remote user/host joins the channel.
            @Override
            public void onUserJoined(int uid, int elapsed) {
                super.onUserJoined(uid, elapsed);
                runOnUiThread(() -> {
                    // Initialize and display remote video view for the new user.
                    setupRemoteVideo(uid);
                    showToast("User joined: " + uid);
                });
            }

            // Triggered when a remote user/host leaves the channel.
            @Override
            public void onUserOffline(int uid, int reason) {
                super.onUserOffline(uid, reason);
                runOnUiThread(() -> {
                    showToast("User offline: " + uid);
                });
            }
        };
        ```
      </TabsContent>

      <TabsContent value="kotlin">
        ```kotlin
        private val mRtcEventHandler = object : IRtcEngineEventHandler() {
            override fun onJoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) {
                super.onJoinChannelSuccess(channel, uid, elapsed)
                runOnUiThread {
                    showToast("Joined channel $channel")
                }
            }
            override fun onUserJoined(uid: Int, elapsed: Int) {
                runOnUiThread {
                    showToast("User joined: $uid")
                }
            }
            override fun onUserOffline(uid: Int, reason: Int) {
                super.onUserOffline(uid, reason)
                runOnUiThread {
                    showToast("User offline: $uid")
                }
            }
        }
        ```
      </TabsContent>
    </Tabs>

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

    Follow these steps to enable the video module:

    1. Call `enableVideo` to enable the video module.
    2. Call `startPreview` to enable local video preview.

    <Tabs defaultValue="java">
      <TabsList>
        <TabsTrigger value="java">
          Java
        </TabsTrigger>

        <TabsTrigger value="kotlin">
          Kotlin
        </TabsTrigger>
      </TabsList>

      <TabsContent value="java">
        ```java
        private void enableVideo() {
            mRtcEngine.enableVideo();
            mRtcEngine.startPreview();
        }
        ```
      </TabsContent>

      <TabsContent value="kotlin">
        ```kotlin
        private fun enableVideo() {
            mRtcEngine?.apply {
                enableVideo()
                startPreview()
            }
        }
        ```
      </TabsContent>
    </Tabs>

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

    Call `setupLocalVideo` to initialize the local view and set the local video display properties.

    <Tabs defaultValue="java">
      <TabsList>
        <TabsTrigger value="java">
          Java
        </TabsTrigger>

        <TabsTrigger value="kotlin">
          Kotlin
        </TabsTrigger>
      </TabsList>

      <TabsContent value="java">
        ```java
        private void setupLocalVideo() {
            FrameLayout container = findViewById(R.id.local_video_view_container);
            SurfaceView surfaceView = new SurfaceView(getBaseContext());
            container.addView(surfaceView);
            mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0));
        }
        ```
      </TabsContent>

      <TabsContent value="kotlin">
        ```kotlin
        /**
         * Initializes the local video view and sets the display properties.
         * This method adds a SurfaceView to the local video container and configures it.
         */
        private fun setupLocalVideo() {
            val container: FrameLayout = findViewById(R.id.local_video_view_container)
            val surfaceView = SurfaceView(baseContext)
            container.addView(surfaceView)
            mRtcEngine.setupLocalVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0))
        }
        ```
      </TabsContent>
    </Tabs>

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

    When a remote user joins the channel, call `setupRemoteVideo` and pass in the remote user's `uid`, obtained from the `onUserJoined` callback, to display the remote video.

    <Tabs defaultValue="java">
      <TabsList>
        <TabsTrigger value="java">
          Java
        </TabsTrigger>

        <TabsTrigger value="kotlin">
          Kotlin
        </TabsTrigger>
      </TabsList>

      <TabsContent value="java">
        ```java
        private void setupRemoteVideo(int uid) {
            FrameLayout container = findViewById(R.id.remote_video_view_container);
            SurfaceView surfaceView = new SurfaceView(getBaseContext());
            surfaceView.setZOrderMediaOverlay(true);
            container.addView(surfaceView);
            mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid));
        }
        ```
      </TabsContent>

      <TabsContent value="kotlin">
        ```kotlin
        private fun setupRemoteVideo(uid: Int) {
            val container = findViewById<FrameLayout>(R.id.remote_video_view_container)
            val surfaceView = SurfaceView(baseContext).apply {
                setZOrderMediaOverlay(true)
            }
            container.addView(surfaceView)
            mRtcEngine.setupRemoteVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid))
        }
        ```
      </TabsContent>
    </Tabs>

    ### Handle permissions [#handle-permissions]

    To access the media devices on Android devices, declare the necessary permissions in the app's manifest and ensure that the user grants these permissions when the client starts.

    1. Open your project's `AndroidManifest.xml` file and add the following permissions before `<application>`:

       ```xml
       <uses-feature android:name="android.hardware.camera" android:required="false" />
       <!--Required permissions-->
       <uses-permission android:name="android.permission.INTERNET"/>
       <uses-permission android:name="android.permission.CAMERA"/>
       <uses-permission android:name="android.permission.RECORD_AUDIO"/>
       <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
       <!--Optional permissions-->
       <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
       <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
       <uses-permission android:name="android.permission.BLUETOOTH"/>
       <!-- For devices running Android 12 (API level 32) or higher and integrating Agora Video SDK version v4.1.0 or lower, you also need to add the following permissions -->
       <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
       <!-- For Android 12.0 or higher, the following permissions are also required -->
       <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
       <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
       ```

    2. Use the following code to handle runtime permissions in your Android app. The logic ensures that the necessary permissions are granted before starting Video Calling. In your `MainActivity` file, add the following code:

    <Tabs defaultValue="java">
      <TabsList>
        <TabsTrigger value="java">
          Java
        </TabsTrigger>

        <TabsTrigger value="kotlin">
          Kotlin
        </TabsTrigger>
      </TabsList>

      <TabsContent value="java">
        ```java
        private static final int PERMISSION_REQ_ID = 22;

            private void requestPermissions() {
                ActivityCompat.requestPermissions(this, getRequiredPermissions(), PERMISSION_REQ_ID);
            }
            private boolean checkPermissions() {
                for (String permission : getRequiredPermissions()) {
                    if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                        return false;
                    }
                }
                return true;
            }
            private String[] getRequiredPermissions() {
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
                    return new String[]{
                        Manifest.permission.RECORD_AUDIO,
                        Manifest.permission.CAMERA,
                        Manifest.permission.READ_PHONE_STATE,
                        Manifest.permission.BLUETOOTH_CONNECT
                    };
                } else {
                    return new String[]{
                        Manifest.permission.RECORD_AUDIO,
                        Manifest.permission.CAMERA
                    };
                }
            }
            @Override
            public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
                if (requestCode == PERMISSION_REQ_ID && checkPermissions()) {
                    startVideoCalling();
                }
            }
        ```
      </TabsContent>

      <TabsContent value="kotlin">
        ```kotlin
        private val PERMISSION_REQ_ID = 22

            private fun requestPermissions() {
                ActivityCompat.requestPermissions(this, getRequiredPermissions(), PERMISSION_REQ_ID)
            }

            private fun checkPermissions(): Boolean {
                for (permission in getRequiredPermissions()) {
                    if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                        return false
                    }
                }
                return true
            }

            private fun getRequiredPermissions(): Array<String> {
                return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                    arrayOf(
                        Manifest.permission.RECORD_AUDIO,
                        Manifest.permission.CAMERA,
                        Manifest.permission.READ_PHONE_STATE,
                        Manifest.permission.BLUETOOTH_CONNECT
                    )
                } else {
                    arrayOf(
                        Manifest.permission.RECORD_AUDIO,
                        Manifest.permission.CAMERA
                    )
                }
            }

            override fun onRequestPermissionsResult(
                requestCode: Int,
                permissions: Array<out String>,
                grantResults: IntArray
            ) {
                super.onRequestPermissionsResult(requestCode, permissions, grantResults)
                if (requestCode == PERMISSION_REQ_ID && checkPermissions()) {
                    startVideoCalling()
                }
            }
        ```
      </TabsContent>
    </Tabs>

    ### Start and close the app [#start-and-close-the-app]

    When a user launches your client, start real-time interaction. When a user closes the app, stop the interaction.

    1. In the `onCreate` callback, check whether the client has been granted the required permissions. If the permissions have not been granted, request the required permissions from the user. If permissions are granted, initialize `RtcEngine` and join a channel.

    <Tabs defaultValue="java">
      <TabsList>
        <TabsTrigger value="java">
          Java
        </TabsTrigger>

        <TabsTrigger value="kotlin">
          Kotlin
        </TabsTrigger>
      </TabsList>

      <TabsContent value="java">
        ```java
        @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                if (checkPermissions()) {
                    startVideoCalling();
                } else {
                    requestPermissions();
                }
            }
        ```
      </TabsContent>

      <TabsContent value="kotlin">
        ```kotlin
        override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                setContentView(R.layout.activity_main)
                if (checkPermissions()) {
                    startVideoCalling()
                } else {
                    requestPermissions()
                }
            }
        ```
      </TabsContent>
    </Tabs>

    2. When a user closes the client, or switches the client to the background, call `stopPreview` to stop the video preview and then call `leaveChannel` to leave the current channel and release all session-related resources.

    <Tabs defaultValue="java">
      <TabsList>
        <TabsTrigger value="java">
          Java
        </TabsTrigger>

        <TabsTrigger value="kotlin">
          Kotlin
        </TabsTrigger>
      </TabsList>

      <TabsContent value="java">
        ```java
        private void cleanupAgoraEngine() {
                if (mRtcEngine != null) {
                    mRtcEngine.stopPreview();
                    mRtcEngine.leaveChannel();
                    mRtcEngine = null;
                }
            }
        ```
      </TabsContent>

      <TabsContent value="kotlin">
        ```kotlin
        private fun cleanupAgoraEngine() {
                mRtcEngine?.apply {
                    stopPreview()
                    leaveChannel()
                }
                mRtcEngine = null
            }
        ```
      </TabsContent>
    </Tabs>

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

    A complete code sample demonstrating the basic process of real-time interaction is provided for your reference. To use the sample code, copy the following lines into the `MainActivity` file in your project. Then, replace `<projectname>` in package `com.example.<projectname>` with your project's name.

    <Accordions>
      <Accordion title="Complete sample code for real-time Video Calling">
        <Tabs defaultValue="java">
          <TabsList>
            <TabsTrigger value="java">
              Java
            </TabsTrigger>

            <TabsTrigger value="kotlin">
              Kotlin
            </TabsTrigger>
          </TabsList>

          <TabsContent value="java">
            ```java
            package com.example.<projectname>;

            import android.Manifest;
            import android.content.pm.PackageManager;
            import android.os.Bundle;
            import android.view.SurfaceView;
            import android.widget.FrameLayout;
            import android.widget.Toast;

            import androidx.annotation.NonNull;
            import androidx.appcompat.app.AppCompatActivity;
            import androidx.core.app.ActivityCompat;
            import androidx.core.content.ContextCompat;

            import io.agora.rtc2.ChannelMediaOptions;
            import io.agora.rtc2.Constants;
            import io.agora.rtc2.IRtcEngineEventHandler;
            import io.agora.rtc2.RtcEngine;
            import io.agora.rtc2.RtcEngineConfig;
            import io.agora.rtc2.video.VideoCanvas;


            public class MainActivity extends AppCompatActivity {
                private static final int PERMISSION_REQ_ID = 22;
                private String myAppId = "<Your app ID>";
                private String channelName = "<Your channel name>";
                private String token = "<Your token>";
                private RtcEngine mRtcEngine;

                private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
                    // Callback when successfully joining the channel
                    @Override
                    public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
                        super.onJoinChannelSuccess(channel, uid, elapsed);
                        showToast("Joined channel " + channel);
                    }
                    // Callback when a remote user or host joins the current channel
                    @Override
                    public void onUserJoined(int uid, int elapsed) {
                        super.onUserJoined(uid, elapsed);
                        runOnUiThread(() -> {
                            // When a remote user joins the channel, display the remote video stream for the specified uid
                            setupRemoteVideo(uid);
                            showToast("User joined: " + uid); // Show toast for user joining
                        });
                    }
                    // Callback when a remote user or host leaves the current channel
                    @Override
                    public void onUserOffline(int uid, int reason) {
                        super.onUserOffline(uid, reason);
                        runOnUiThread(() -> {
                            showToast("User offline: " + uid); // Show toast for user going offline
                        });
                    }
                };

                @Override
                protected void onCreate(Bundle savedInstanceState) {
                    super.onCreate(savedInstanceState);
                    setContentView(R.layout.activity_main);
                    if (checkPermissions()) {
                        startVideoCalling();
                    } else {
                        requestPermissions();
                    }
                }

                private void requestPermissions() {
                    ActivityCompat.requestPermissions(this, getRequiredPermissions(), PERMISSION_REQ_ID);
                }

                private boolean checkPermissions() {
                    for (String permission : getRequiredPermissions()) {
                        if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                            return false;
                        }
                    }
                    return true;
                }

                private String[] getRequiredPermissions() {
                    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
                        return new String[]{
                            Manifest.permission.RECORD_AUDIO,
                            Manifest.permission.CAMERA,
                            Manifest.permission.READ_PHONE_STATE,
                            Manifest.permission.BLUETOOTH_CONNECT
                        };
                    } else {
                        return new String[]{
                            Manifest.permission.RECORD_AUDIO,
                            Manifest.permission.CAMERA
                        };
                    }
                }

                @Override
                public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
                    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
                    if (requestCode == PERMISSION_REQ_ID && checkPermissions()) {
                        startVideoCalling();
                    }
                }

                private void startVideoCalling() {
                    initializeAgoraVideoSDK();
                    enableVideo();
                    setupLocalVideo();
                    joinChannel();
                }

                private void initializeAgoraVideoSDK() {
                    try {
                        RtcEngineConfig config = new RtcEngineConfig();
                        config.mContext = getBaseContext();
                        config.mAppId = myAppId;
                        config.mEventHandler = mRtcEventHandler;
                        mRtcEngine = RtcEngine.create(config);
                    } catch (Exception e) {
                        throw new RuntimeException("Error initializing RTC engine: " + e.getMessage());
                    }
                }

                private void enableVideo() {
                    mRtcEngine.enableVideo();
                    mRtcEngine.startPreview();
                }

                private void setupLocalVideo() {
                    FrameLayout container = findViewById(R.id.local_video_view_container);
                    SurfaceView surfaceView = new SurfaceView(getBaseContext());
                    container.addView(surfaceView);
                    mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0));
                }

                private void joinChannel() {
                    ChannelMediaOptions options = new ChannelMediaOptions();
                    options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
                    options.channelProfile = Constants.CHANNEL_PROFILE_COMMUNICATION;
                    options.publishCameraTrack = true;
                    options.publishMicrophoneTrack = true;
                    mRtcEngine.joinChannel(token, channelName, 0, options);
                }

                private void setupRemoteVideo(int uid) {
                    FrameLayout container = findViewById(R.id.remote_video_view_container);
                    SurfaceView surfaceView = new SurfaceView(getBaseContext());
                    surfaceView.setZOrderMediaOverlay(true);
                    container.addView(surfaceView);
                    mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid));
                }

                @Override
                protected void onDestroy() {
                    super.onDestroy();
                    cleanupAgoraEngine();
                }

                private void cleanupAgoraEngine() {
                    if (mRtcEngine != null) {
                        mRtcEngine.stopPreview();
                        mRtcEngine.leaveChannel();
                        mRtcEngine = null;
                    }
                }

                private void showToast(String message) {
                    runOnUiThread(() -> Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show());
                }
            }
            ```
          </TabsContent>

          <TabsContent value="kotlin">
            ```kotlin
            package com.example.<projectname>

            import android.Manifest
            import android.content.pm.PackageManager
            import android.os.Bundle
            import android.view.SurfaceView
            import android.widget.FrameLayout
            import android.widget.Toast
            import androidx.appcompat.app.AppCompatActivity
            import androidx.core.app.ActivityCompat
            import androidx.core.content.ContextCompat
            import io.agora.rtc2.ChannelMediaOptions
            import io.agora.rtc2.Constants
            import io.agora.rtc2.IRtcEngineEventHandler
            import io.agora.rtc2.RtcEngine
            import io.agora.rtc2.RtcEngineConfig
            import io.agora.rtc2.video.VideoCanvas


            class MainActivity : AppCompatActivity() {

                companion object {
                    private const val PERMISSION_REQ_ID = 22
                }

                private val myAppId = "<Your app ID>"
                private val channelName = "<Your channel name>"
                private val token = "<Your token>"
                private var mRtcEngine: RtcEngine? = null

                private val mRtcEventHandler = object : IRtcEngineEventHandler() {
                    override fun onJoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) {
                        super.onJoinChannelSuccess(channel, uid, elapsed)
                        showToast("Joined channel $channel")
                    }

                    override fun onUserJoined(uid: Int, elapsed: Int) {
                        super.onUserJoined(uid, elapsed)
                        runOnUiThread {
                            setupRemoteVideo(uid)
                            showToast("User joined: $uid")
                        }
                    }

                    override fun onUserOffline(uid: Int, reason: Int) {
                        super.onUserOffline(uid, reason)
                        runOnUiThread {
                            showToast("User offline: $uid")
                        }
                    }
                }

                override fun onCreate(savedInstanceState: Bundle?) {
                    super.onCreate(savedInstanceState)
                    setContentView(R.layout.activity_main)

                    if (checkPermissions()) {
                        startVideoCalling()
                    } else {
                        requestPermissions()
                    }
                }

                private fun requestPermissions() {
                    ActivityCompat.requestPermissions(this, getRequiredPermissions(), PERMISSION_REQ_ID)
                }

                private fun checkPermissions(): Boolean {
                    return getRequiredPermissions().all {
                        ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
                    }
                }

                private fun getRequiredPermissions(): Array<String> {
                    return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
                        arrayOf(
                            Manifest.permission.RECORD_AUDIO,
                            Manifest.permission.CAMERA,
                            Manifest.permission.READ_PHONE_STATE,
                            Manifest.permission.BLUETOOTH_CONNECT
                        )
                    } else {
                        arrayOf(
                            Manifest.permission.RECORD_AUDIO,
                            Manifest.permission.CAMERA
                        )
                    }
                }

                override fun onRequestPermissionsResult(
                    requestCode: Int,
                    permissions: Array<out String>,
                    grantResults: IntArray
                ) {
                    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
                    if (requestCode == PERMISSION_REQ_ID && checkPermissions()) {
                        startVideoCalling()
                    }
                }

                private fun startVideoCalling() {
                    initializeAgoraVideoSDK()
                    enableVideo()
                    setupLocalVideo()
                    joinChannel()
                }

                private fun initializeAgoraVideoSDK() {
                    try {
                        val config = RtcEngineConfig().apply {
                            mContext = applicationContext
                            mAppId = myAppId
                            mEventHandler = mRtcEventHandler
                        }
                        mRtcEngine = RtcEngine.create(config)
                    } catch (e: Exception) {
                        throw RuntimeException("Error initializing RTC engine: ${e.message}")
                    }
                }

                private fun enableVideo() {
                    mRtcEngine?.apply {
                        enableVideo()
                        startPreview()
                    }
                }

                private fun setupLocalVideo() {
                    val container: FrameLayout = findViewById(R.id.local_video_view_container)
                    val surfaceView = SurfaceView(baseContext)
                    container.addView(surfaceView)
                    mRtcEngine?.setupLocalVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0))
                }

                private fun joinChannel() {
                    val options = ChannelMediaOptions().apply {
                        clientRoleType = Constants.CLIENT_ROLE_BROADCASTER
                        channelProfile = Constants.CHANNEL_PROFILE_COMMUNICATION
                        publishMicrophoneTrack = true
                        publishCameraTrack = true
                    }
                    mRtcEngine?.joinChannel(token, channelName, 0, options)
                }

                private fun setupRemoteVideo(uid: Int) {
                    val container: FrameLayout = findViewById(R.id.remote_video_view_container)
                    val surfaceView = SurfaceView(applicationContext).apply {
                        setZOrderMediaOverlay(true)
                        container.addView(this)
                    }
                    mRtcEngine?.setupRemoteVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid))
                }

                override fun onDestroy() {
                    super.onDestroy()
                    cleanupAgoraEngine()
                }

                private fun cleanupAgoraEngine() {
                    mRtcEngine?.apply {
                        stopPreview()
                        leaveChannel()
                    }
                    mRtcEngine = null
                }

                private fun showToast(message: String) {
                    runOnUiThread {
                        Toast.makeText(this@MainActivity, message, Toast.LENGTH_SHORT).show()
                    }
                }
            }
            ```
          </TabsContent>
        </Tabs>
      </Accordion>
    </Accordions>

    <CalloutContainer type="info">
      <CalloutDescription>
        For the `myAppId` and `token` variables, replace the placeholders with the values you obtained from Agora Console. Ensure you enter the same `channelName` you used when generating the temporary token.
      </CalloutDescription>
    </CalloutContainer>

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

    To connect the sample code to your existing UI, ensure that your XML layout includes the container UI element IDs used to [Display the local video](#display-the-local-video) and [Display remote video](#display-remote-video).

    Alternatively, use the following sample code to generate a basic user interface. Replace the existing content in `/app/src/main/res/layout/activity_main.xml` with this code.

    ![UI design](https://assets-docs.agora.io/images/video-sdk/quickstart-ui-android-design.png)

    ### Sample code to create the user interface [#sample-code-to-create-the-user-interface]

    ```xml
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <FrameLayout
            android:id="@+id/local_video_view_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/white" />

        <FrameLayout
            android:id="@+id/remote_video_view_container"
            android:layout_width="160dp"
            android:layout_height="160dp"
            android:layout_marginEnd="16dp"
            android:layout_marginTop="16dp"
            android:background="@android:color/darker_gray"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
    ```

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

    Take the following steps to test the sample code:

    1. In `MainActivity` update the values for `myAppId`, and `token` with values from Agora Console. Fill in the same `channelName` you used to generate the token.

    2. Enable developer options on your Android test device. Turn on USB debugging, connect the Android device to your development machine through a USB cable, and check that your device appears in the Android device options.

    3. In Android Studio, click ![](https://assets-docs.agora.io/images/video-sdk/icon_android_gradle_sync.png) **Sync Project with Gradle Files** to resolve project dependencies and update the configuration.

    4. After synchronization is successful, click ![](https://assets-docs.agora.io/images/video-sdk/icon_android_run.png) **Run app**. Android Studio starts compilation. After a few moments, the app is installed on your Android device.

    5. Launch the App, grant recording and camera permissions. If you set the user role to host, you will see yourself in the local view.

    6. On a second Android device, repeat the previous steps to install and launch the client. Alternatively, use the [Web demo](https://webdemo.agora.io/basicVideoCall/index.html) to join the same channel and test the following use-cases:

       * If users on both devices join the channel as hosts, they can see and hear each other.
       * If one user joins as host and the other as audience, the host can see themselves in the local video window; the audience can see the host in the remote video window and hear the host.

    ## Reference [#reference]

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

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

    ### Next steps [#next-steps]

    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](/en/realtime-media/video/build/authenticate-users/authentication-workflow).

    ### Sample project [#sample-project]

    Agora provides open source sample projects on [GitHub](https://github.com/AgoraIO/API-Examples) for your reference. Download or view the [JoinChannelVideo](https://github.com/AgoraIO-Community/Agora-RTC-QuickStart/tree/main/Android/Agora-RTC-QuickStart-Android) project for a more detailed example.

    ### API reference [#api-reference]

    * [`RtcEngineConfig`](https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_rtcengineconfig.html)

    * [`create`](https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_irtcengine.html#api_irtcengine_create)

    * [`ChannelMediaOptions`](https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_channelmediaoptions.html)

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

    * [`enableVideo`](https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_irtcengine.html#api_irtcengine_enablevideo)

    * [`startPreview`](https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_irtcengine.html#api_irtcengine_startpreview)

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

    * [`IRtcEngineEventHandler`](https://api-ref.agora.io/en/video-sdk/android/4.x/API/class_irtcengineeventhandler.html#class_irtcengineeventhandler)

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

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

    * [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)

    * [Why do apps on some Android versions fail to capture audio and video after screen locking or switching to the background?](/en/api-reference/faq/quality/android_background)

    ### See also [#see-also]

    * [SDK error codes](/en/realtime-media/video/reference/error-codes)
    * [Connection status management](/en/realtime-media/video/build/manage-connection-and-quality/connection-status-management)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="ios">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />

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

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

    To start a Video Calling session, implement the following steps in your app:

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

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

    * **Send and receive audio and video**: All users can publish streams to the channel and subscribe to audio and video streams published by other users in the channel.

    ![Video calling workflow](https://assets-docs.agora.io/images/video-sdk/video-call.svg)

    ## Prerequisites [#prerequisites-1]

    * A camera and a microphone.
    * A valid Agora account and project. See [Agora account management](/en/realtime-media/video/manage-agora-account) for details.
    * Xcode 13.0 or higher.
    * An Apple developer account.
    * If you use CocoaPods to integrate the SDK, make sure [CocoaPods is installed](https://guides.cocoapods.org/using/getting-started.html#getting-started).
    * Two devices running iOS 14.0 or higher.

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

    <Tabs defaultValue="new">
      <TabsList>
        <TabsTrigger value="new">
          Create a new project
        </TabsTrigger>

        <TabsTrigger value="existing">
          Add to an existing project
        </TabsTrigger>
      </TabsList>

      <TabsContent value="new">
        Follow these steps to create a project in Xcode:

        1. Refer to [Create a project](https://help.apple.com/xcode/mac/current/#/dev07db0e578). Under **Application**, select **App**. Use **Storyboard** for the user interface and choose **Swift** as the programming language.

           <CalloutContainer type="info">
             <CalloutTitle>
               Information
             </CalloutTitle>

             <CalloutDescription>
               If you have not added the development team information, you see the &#x2A;*Add account...** button. Click the button and follow the on-screen prompts to log in to your Apple ID. Once login is complete, click **Next**, and choose your Apple account as the development team.
             </CalloutDescription>
           </CalloutContainer>

        2. [Set up automatic signing](https://help.apple.com/xcode/mac/current/#/dev23aab79b4) for your project.

        3. [Set the target devices](https://help.apple.com/xcode/mac/current/#/deve69552ee5) where your app will be deployed.

        4. Create a user interface for your app. Refer to [Create a user interface](#create-a-user-interface) to create a bare-bones UI.
      </TabsContent>

      <TabsContent value="existing">
        Follow these steps to add Video Calling to your Xcode project:

        1. Open your project in Xcode.
        2. [Set the target devices](https://help.apple.com/xcode/mac/current/#/deve69552ee5) where your app will be deployed.
        3. Create a user interface for your app. Refer to [Create a user interface](#create-a-user-interface) to create a bare-bones UI.
      </TabsContent>
    </Tabs>

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

    Use one of the following methods to install the Video SDK.

    <Tabs defaultValue="spm">
      <TabsList>
        <TabsTrigger value="spm">
          Swift Package Manager
        </TabsTrigger>

        <TabsTrigger value="cocoapods">
          CocoaPods
        </TabsTrigger>

        <TabsTrigger value="manual">
          Manual integration
        </TabsTrigger>
      </TabsList>

      <TabsContent value="spm">
        1. In Xcode, go to **File** > **Add Package Dependencies**.

        2. In the search bar, paste the following URL:

           ```text
           https://github.com/AgoraIO/AgoraRtcEngine_iOS.git
           ```

        3. Click **Add Package**, select the latest version, and click **Next**.

        4. For basic Video Calling, select **RtcBasic**.

           If needed, also select:

           * **`SpatialAudio`** for spatial audio effects.
           * **`VirtualBackground`** for virtual background.

        5. Under **Add to Target**, select your project and click **Add Package**.

           For more information, see [Apple's official documentation](https://help.apple.com/xcode/mac/current/#/devb83d64851).
      </TabsContent>

      <TabsContent value="cocoapods">
        1. Go to the project root directory in Terminal and run `pod init`. A text file named `Podfile` is generated in the project folder.

        2. Open `Podfile` and modify the content as follows. Replace `Your App` with your target name.

           ```ruby
           platform :ios, '9.0'
           target 'Your App' do
             # Replace x.y.z with the specific SDK version number, such as 4.4.0.
             # Integrate the Full SDK
             pod 'AgoraRtcEngine_iOS', 'x.y.z'

             # To integrate the Lite SDK, use the following line instead
             # pod 'AgoraLite_iOS', '4.4.0'
           end
           ```

           Get the latest version number from the [release notes](/en/realtime-media/video/reference/release-notes).

        3. Run `pod install` in Terminal to install the Video SDK. After successful installation, Terminal shows &#x2A;*Pod installation complete!**.

        4. After successful installation, a file with the suffix `.xcworkspace` is generated in the project folder. Open the file in Xcode for subsequent operations.
      </TabsContent>

      <TabsContent value="manual">
        1. Download the latest version of the SDK from [SDKs download](/en/api-reference/sdks?platform=ios) and extract the contents.
        2. Copy the files in the `libs` folder of the SDK package to your project directory.
        3. Open Xcode and [add the corresponding dynamic library](https://help.apple.com/xcode/mac/current/#/dev51a648b07). Make sure the **Embed** property of the added dynamic library is set to **Embed & Sign**.

           <CalloutContainer type="info">
             <CalloutTitle>
               Information
             </CalloutTitle>

             <CalloutDescription>
               Agora SDK uses `libc++` (LLVM) by default. If you need to use `libstdc++` (GNU), contact `support@agora.io`. The library provided by the SDK is a FAT image that includes simulator and device builds.
             </CalloutDescription>
           </CalloutContainer>
      </TabsContent>
    </Tabs>

    <CalloutContainer type="warning">
      <CalloutTitle>
        Note
      </CalloutTitle>

      <CalloutDescription>
        The [privacy updates for App Store submissions](https://developer.apple.com/news/?id=r1henawx) released by Apple require developers to declare approved reasons for using a set of APIs in their app's privacy manifest. Agora provides a [`PrivacyInfo.xcprivacy`](https://download.agora.io/sdk/release/PrivacyInfo.xcprivacy) file that you can include in your project.
      </CalloutDescription>
    </CalloutContainer>

    ## Implement Video Calling [#implement-video-calling-1]

    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">
        ![Video Calling 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.

    ### Import Agora framework [#import-agora-framework]

    Add the following import to your swift file:

    ```swift
    import UIKit
    import AgoraRtcKit
    ```

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

    Call `sharedEngine(withAppId:delegate:)` to create and initialize an `AgoraRtcEngineKit` instance. Provide your [App ID](/en/realtime-media/video/manage-agora-account#get-the-app-id) and an `AgoraRtcEngineDelegate` implementation to [handle SDK events](#subscribe-to-video-sdk-events).

    ```swift
    var agoraKit: AgoraRtcEngineKit!
    let appId = "YOUR_AGORA_APP_ID"

    // Initialize the Agora engine
    func initializeAgoraVideoSDK() {
        agoraKit = AgoraRtcEngineKit.sharedEngine(withAppId: appId, delegate: self)
    }
    ```

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

    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](/en/realtime-media/video/build/authenticate-users/deploy-token-server) in your security infrastructure. For the purpose of this guide [Generate a temporary token](/en/realtime-media/video/manage-agora-account#generate-temporary-tokens).

    * **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 `didJoinChannel` callback.

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

    For Video Calling, set the `channelProfile` to `.communication` and the `clientRoleType` to `.broadcaster`.

    ```swift
    let channelName = "demo"    // Replace with your actual channel name
    let token = "<Authentication token>" // Replace with your token

    // Join the channel with specified options
    func joinChannel() {
        let options = AgoraRtcChannelMediaOptions()
        // In video calling, set the channel use-case to communication
        options.channelProfile = .communication
        // Set the user role as broadcaster (default is audience)
        options.clientRoleType = .broadcaster
        // Publish audio captured by microphone
        options.publishMicrophoneTrack = true
        // Publish video captured by camera
        options.publishCameraTrack = true
        // Auto subscribe to all audio streams
        options.autoSubscribeAudio = true
        // Auto subscribe to all video streams
        options.autoSubscribeVideo = true
        // Use a temporary Token to join the channel
        agoraKit.joinChannel(
            byToken: token,
            channelId: channelName,
            uid: 0,
            mediaOptions: options
        )
    }
    ```

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

    The Video SDK provides a delegate for handling channel events. To use it, conform to the `AgoraRtcEngineDelegate` protocol in your class and implement the event methods you want to handle. The following code implements the `didJoinChannel`, `didOfflineOfUid`, and `didJoinedOfUid` callbacks:

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

    ```swift
    // Extension for handling Agora SDK callbacks
    extension ViewController: AgoraRtcEngineDelegate {

        // Triggered when the local user successfully joins a channel
        func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) {
            print("Successfully joined channel: \(channel) with UID: \(uid)")
        }

        // Triggered when a remote user joins the channel
        func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
            setupRemoteVideo(uid: uid, view: remoteView)
        }
        // Triggered when a remote user leaves the channel
        func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {
            setupRemoteVideo(uid: uid, view: nil)
        }
    }
    ```

    To learn about the other SDK events, see [`AgoraRtcEngineDelegate`](https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit/agorartcenginedelegate).

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

    Follow the steps below to set up the video module.

    1. To enable the video module, call `enableVideo`.
    2. To enable local video preview, call `startPreview`.

       ```swift
       // Enable video functionality (audio is enabled by default)
       agoraKit.enableVideo()
       // Enable local video preview
       agoraKit.startPreview()
       ```

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

    Call `setupLocalVideo` to initialize the local view and set the local video display properties.

    ```swift
    // Configures and starts displaying the local video feed
    func setupLocalVideo() {
        let videoCanvas = AgoraRtcVideoCanvas()
        videoCanvas.view = localView
        videoCanvas.uid = 0  // UID 0 is assigned to the local user
        videoCanvas.renderMode = .hidden
        agoraKit.setupLocalVideo(videoCanvas)
    }
    ```

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

    To initialize the remote user view, call `setupRemoteVideo` and set the local display properties for the remote user. Use the `didJoinedOfUid` callback to get the UID of the remote user.

    ```swift
    func setupRemoteVideo(uid: UInt, view: UIView?) {
        let videoCanvas = AgoraRtcVideoCanvas()
        videoCanvas.uid = uid
        videoCanvas.view = view // Assign view for joining, set to nil for leaving
        videoCanvas.renderMode = .hidden
        agoraKit.setupRemoteVideo(videoCanvas)
    }
    ```

    ### Handle permissions [#handle-permissions-1]

    To access the camera and microphone on Video Calling devices, add the required permissions for real-time interaction. Open the `info.plist` file from the project navigation bar, [edit the property list](https://help.apple.com/xcode/mac/current/#/dev3f399a2a6), to add the required permissions. These permissions are optional. However, if you do not add these permissions, you will not be able to use the corresponding devices.

    | Key                                    | Type   | Value                                                                                                   |
    | :------------------------------------- | :----- | :------------------------------------------------------------------------------------------------------ |
    | Privacy - Microphone Usage Description | String | For the purpose of using the microphone. For example, for a call or live interactive streaming session. |
    | Privacy - Camera Usage Description     | String | For the purpose of using the camera. For example, for a call or live interactive streaming session.     |

    <CalloutContainer type="info">
      <CalloutDescription>
        * If your project depends on third-party plugins or libraries, such as a third-party camera library, and the signature of the plug-in or library is inconsistent with the signature of the project, check the **Hardened Runtime** settings. Specifically, review and potentially disable **Runtime Exceptions** and **Library Validation** in the project configuration.
        * For further information, refer to [Preparing your app for distribution](https://developer.apple.com/documentation/xcode/preparing_your_app_for_distribution).
      </CalloutDescription>
    </CalloutContainer>

    ### Start and close the app [#start-and-close-the-app-1]

    When the user launches the app, it joins the channel and starts Video Calling. When the user closes the app, it leaves the channel and ends Video Calling.

    1. To start Video Calling, call the following methods:

       ```swift
       // Initialize the Agora engine
       initializeAgoraVideoSDK()
       // Start the local video preview
       setupLocalVideo()
       // Join an Agora channel
       joinChannel()
       ```

    2. To leave the channel and release SDK resources when the app is closed, call the following methods:

       ```swift
       // Stop local video preview
       agoraKit.stopPreview()
       // Leave the channel and release session-related resources
       agoraKit.leaveChannel(nil)
       // Release all resources used by the Agora SDK
       AgoraRtcEngineKit.destroy()
       ```

    <CalloutContainer type="warning">
      <CalloutTitle>
        caution
      </CalloutTitle>

      <CalloutDescription>
        After destroying the engine, you can no longer use SDK methods and callbacks. To use the real-time interaction functions again, create a new engine. See [Initialize the engine](#initialize-the-engine) for details.
      </CalloutDescription>
    </CalloutContainer>

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

    A complete code sample demonstrating the basic process of real-time interaction is provided for your reference. Copy the following code into your `ViewController.swift` file:

    <Accordions>
      <Accordion title="Complete sample code for real-time Video Calling">
        ```swift
        import UIKit
        import AgoraRtcKit

        class ViewController: UIViewController {

            let appId = "<Your app ID>" // Replace with your actual App ID
            let channelName = "demo"    // Replace with your actual channel name
            let token = "<Authentication token>" // Replace with your token

            // UI view for displaying the local video stream
            var localView: UIView!
            // UI view for displaying the remote video stream
            var remoteView: UIView!
            // Instance of the Agora RTC engine
            var agoraKit: AgoraRtcEngineKit!

            override func viewDidLoad() {
                super.viewDidLoad()

                // Initialize the Agora engine
                initializeAgoraVideoSDK()
                // Set up the user interface
                setupUI()
                // Start the local video preview
                setupLocalVideo()
                // Join an Agora channel
                joinChannel()
            }

            // Clean up resources when the view controller is deallocated
            deinit {
                agoraKit.stopPreview()
                agoraKit.leaveChannel(nil)
                AgoraRtcEngineKit.destroy()
            }

            // Initializes the Video SDK instance
            func initializeAgoraVideoSDK() {
                // Create an instance of AgoraRtcEngineKit and set the delegate
                agoraKit = AgoraRtcEngineKit.sharedEngine(withAppId: appId, delegate: self)
            }

            // Sets up the UI layout for local and remote video views
            func setupUI() {
                // Create the local video view covering the full screen
                localView = UIView(frame: UIScreen.main.bounds)

                // Create the remote video view positioned in the top-right corner
                remoteView = UIView(frame: CGRect(x: self.view.bounds.width - 135, y: 50, width: 135, height: 240))

                // Add video views to the main view
                self.view.addSubview(localView)
                self.view.addSubview(remoteView)
            }

            // Configures and starts displaying the local video feed
            func setupLocalVideo() {
                // Enable video functionality (audio is enabled by default)
                agoraKit.enableVideo()
                let videoCanvas = AgoraRtcVideoCanvas()
                videoCanvas.view = localView
                videoCanvas.uid = 0  // UID 0 is assigned to the local user
                videoCanvas.renderMode = .hidden
                agoraKit.setupLocalVideo(videoCanvas)
                agoraKit.startPreview()
            }

            // Join the channel with specified options
            func joinChannel() {
                let options = AgoraRtcChannelMediaOptions()
                // In video calling, set the channel use-case to communication
                options.channelProfile = .communication
                // Set the user role as broadcaster (default is audience)
                options.clientRoleType = .broadcaster
                // Publish audio captured by microphone
                options.publishMicrophoneTrack = true
                // Publish video captured by camera
                options.publishCameraTrack = true
                // Auto subscribe to all audio streams
                options.autoSubscribeAudio = true
                // Auto subscribe to all video streams
                options.autoSubscribeVideo = true
                // If you set uid=0, the engine generates a uid internally; on success, it triggers didJoinChannel callback
                // Join the channel with a temporary token
                agoraKit.joinChannel(
                    byToken: token,
                    channelId: channelName,
                    uid: 0,
                    mediaOptions: options
                )
            }

            func setupRemoteVideo(uid: UInt, view: UIView?) {
                let videoCanvas = AgoraRtcVideoCanvas()
                videoCanvas.uid = uid
                videoCanvas.view = view // Assign view for joining, set to nil for leaving
                videoCanvas.renderMode = .hidden
                agoraKit.setupRemoteVideo(videoCanvas)
            }
        }

        // Extension for handling Agora SDK callbacks
        extension ViewController: AgoraRtcEngineDelegate {

            // Triggered when the local user successfully joins a channel
            func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) {
                print("Successfully joined channel: \\(channel) with UID: \\(uid)")
            }

            // Triggered when a remote user joins the channel
            func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
                setupRemoteVideo(uid: uid, view: remoteView)
            }

            // Triggered when a remote user leaves the channel
            func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {
                setupRemoteVideo(uid: uid, view: nil)
            }
        }
        ```
      </Accordion>
    </Accordions>

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

    To connect the sample code to your existing UI, ensure that your `ViewController.swift` file includes the `UIView`s used to [Display the local video](#display-the-local-video) and [Display remote video](#display-remote-video).

    Alternatively, use the following sample code to generate a basic user interface. To use this interface, replace the contents of the `ViewController.swift` file with the following code:

    ### Sample code to create the user interface [#sample-code-to-create-the-user-interface-1]

    ```swift
    // ViewController.swift
    import UIKit

    class ViewController: UIViewController {

        // UI view for displaying the local video stream
        var localView: UIView!
        // UI view for displaying the remote video stream
        var remoteView: UIView!

        override func viewDidLoad() {
            super.viewDidLoad()

            // Set up the user interface
            setupUI()
        }

        // Sets up the UI layout for local and remote video views
        func setupUI() {
            // Create the local video view covering the full screen
            localView = UIView(frame: UIScreen.main.bounds)

            // Create the remote video view positioned in the top-right corner
            remoteView = UIView(frame: CGRect(x: self.view.bounds.width - 135, y: 50, width: 135, height: 240))

            // Add video views to the main view
            self.view.addSubview(localView)
            self.view.addSubview(remoteView)
        }
    }
    ```

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

    Take the following steps to test the sample code:

    1. In your code update the `appId` and `token`, with the app ID and temporary token you obtained from Agora Console. Use the same `channelName` you filled in when generating the temporary token.

    2. Connect your iOS device to your computer.

    3. Click **Build** to run your project and wait a few seconds for the app installation to complete.

    4. Allow the app to access the device's microphone and camera.

    5. If an untrusted developer prompt pops up on the device, click **Cancel** to close the prompt, then open **Settings > General > VPN and Device Management** on the iOS device, and choose to trust the developer in the **Developer APP**.

    6. On a second Video Calling device, repeat the previous steps to install and launch the client. Alternatively, use the [Web demo](https://webdemo.agora.io/basicVideoCall/index.html) to join the same channel and test the following use-cases:

       * If users on both devices join the channel as hosts, they can see and hear each other.
       * If one user joins as host and the other as audience, the host can see themselves in the local video window; the audience can see the host in the remote video window and hear the host.

    ## Reference [#reference-1]

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

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

    ### Next steps [#next-steps-1]

    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](/en/realtime-media/video/build/authenticate-users/authentication-workflow).

    ### Sample project [#sample-project-1]

    Agora provides an open source sample project on [GitHub](https://github.com/AgoraIO-Community/Agora-RTC-QuickStart/tree/main/iOS/Agora-RTC-QuickStart-iOS) for your reference. Download it or view the source code for a more detailed example.

    ### Add a privacy manifest file [#add-a-privacy-manifest-file]

    The Agora Video SDK for Video Calling provides the `PrivacyInfo.xcprivacy` file that contains the required reasons for the APIs used by the SDK. To add the privacy manifest to your app in Xcode, follow these steps:

    1. Create a privacy manifest in your app project:

       1. Choose **File > New File**.
       2. Scroll down to the **Resource** section and select **App Privacy File** type.
       3. Click **Next**.
       4. Check your app in the **Targets** list.
       5. Click **Create**.

       The default file name is `PrivacyInfo.xcprivacy`, which is also the required file name for bundled privacy manifests.

    2. Add the items in the SDK `PrivacyInfo.xcprivacy` file to your app `PrivacyInfo.xcprivacy` file using the following source code:

       ```xml
       <?xml version="1.0" encoding="UTF-8"?>
       <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
       <plist version="1.0">
       <dict>
           <key>NSPrivacyTracking</key>
           <false/>
           <key>NSPrivacyCollectedDataTypes</key>
           <array/>
           <key>NSPrivacyAccessedAPITypes</key>
           <array>
               <dict>
                   <key>NSPrivacyAccessedAPIType</key>
                   <string>NSPrivacyAccessedAPICategorySystemBootTime</string>
                   <key>NSPrivacyAccessedAPITypeReasons</key>
                   <array>
                       <string>35F9.1</string>
                   </array>
               </dict>
               <dict>
                   <key>NSPrivacyAccessedAPIType</key>
                   <string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
                   <key>NSPrivacyAccessedAPITypeReasons</key>
                   <array>
                       <string>DDA9.1</string>
                   </array>
               </dict>
               <dict>
                   <key>NSPrivacyAccessedAPIType</key>
                   <string>NSPrivacyAccessedAPICategoryDiskSpace</string>
                   <key>NSPrivacyAccessedAPITypeReasons</key>
                   <array>
                       <string>E174.1</string>
                   </array>
               </dict>
           </array>
       </dict>
       </plist>
       ```

    ### API reference [#api-reference-1]

    * [`sharedEngine`](https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit/agorartcenginekit/sharedengine\(withappid\:delegate:\))
    * [`joinChannel`](https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit/agorartcenginekit/joinchannel\(bytoken\:channelid\:uid\:mediaoptions\:joinsuccess:\))
    * [`enableVideo`](https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit/agorartcenginekit/enablevideo\(\))
    * [`startPreview`](https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit/agorartcenginekit/startpreview\(\))
    * [`leaveChannel`](https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit/agorartcenginekit/leavechannel\(_:\))
    * [`enableMultiCamera`](https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit/agorartcenginekit/enablemulticamera\(_\:config:\))
    * [`AgoraRtcEngineDelegate`](https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit/agorartcenginedelegate)

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

    * [How can I fix black screen issues?](/en/api-reference/faq/quality/video_blank#black-screen-on-the-local-side)
    * [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)
    * [How can I troubleshoot the issue of no sound?](/en/api-reference/faq/quality/audio_noaudio)
    * [How can I add a privacy manifest to my iOS app?](/en/api-reference/faq/other/ios_privacy_manifest)

    ### See also [#see-also-1]

    * [SDK error codes](/en/realtime-media/video/reference/error-codes)
    * [Connection status management](/en/realtime-media/video/build/manage-connection-and-quality/connection-status-management)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="macos">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="macos" />

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

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

    To start a Video Calling session, implement the following steps in your app:

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

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

    * **Send and receive audio and video**: All users can publish streams to the channel and subscribe to audio and video streams published by other users in the channel.

    ![Video calling workflow](https://assets-docs.agora.io/images/video-sdk/video-call.svg)

    ## Prerequisites [#prerequisites-2]

    * A camera and a microphone.
    * A valid Agora account and project. See [Agora account management](/en/realtime-media/video/manage-agora-account) for details.
    * Xcode 13.0 or higher.
    * An Apple developer account.
    * If you use CocoaPods to integrate the SDK, make sure [CocoaPods is installed](https://guides.cocoapods.org/using/getting-started.html#getting-started).
    * Two devices running macOS 10.10 or higher.

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

    <Tabs defaultValue="new">
      <TabsList>
        <TabsTrigger value="new">
          Create a new project
        </TabsTrigger>

        <TabsTrigger value="existing">
          Add to an existing project
        </TabsTrigger>
      </TabsList>

      <TabsContent value="new">
        Follow these steps to create a project in Xcode:

        1. Refer to [Create a project](https://help.apple.com/xcode/mac/current/#/dev07db0e578). Under **Application**, select **App**. Use **Storyboard** for the user interface and choose **Swift** as the programming language.

           <CalloutContainer type="info">
             <CalloutTitle>
               Information
             </CalloutTitle>

             <CalloutDescription>
               If you have not added the development team information, you see the &#x2A;*Add account...** button. Click the button and follow the on-screen prompts to log in to your Apple ID. Once login is complete, click **Next**, and choose your Apple account as the development team.
             </CalloutDescription>
           </CalloutContainer>

        2. [Set up automatic signing](https://help.apple.com/xcode/mac/current/#/dev23aab79b4) for your project.

        3. [Set the target devices](https://help.apple.com/xcode/mac/current/#/deve69552ee5) where your app will be deployed.

        4. Create a user interface for your app. Refer to [Create a user interface](#create-a-user-interface) to create a bare-bones UI.
      </TabsContent>

      <TabsContent value="existing">
        Follow these steps to add Video Calling to your Xcode project:

        1. Open your project in Xcode.
        2. [Set the target devices](https://help.apple.com/xcode/mac/current/#/deve69552ee5) where your app will be deployed.
        3. Create a user interface for your app. Refer to [Create a user interface](#create-a-user-interface) to create a bare-bones UI.
      </TabsContent>
    </Tabs>

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

    Use one of the following methods to install the Video SDK.

    <Tabs defaultValue="spm">
      <TabsList>
        <TabsTrigger value="spm">
          Swift Package Manager
        </TabsTrigger>

        <TabsTrigger value="cocoapods">
          CocoaPods
        </TabsTrigger>

        <TabsTrigger value="manual">
          Manual integration
        </TabsTrigger>
      </TabsList>

      <TabsContent value="spm">
        1. In Xcode, go to **File** > **Add Package Dependencies**.

        2. In the search bar, paste the following URL:

           ```text
           https://github.com/AgoraIO/AgoraRtcEngine_macOS.git
           ```

        3. Click **Add Package**, select the latest version, and click **Next**.

        4. For basic Video Calling, select **RtcBasic**.

           If needed, also select:

           * **`SpatialAudio`** for spatial audio effects.
           * **`VirtualBackground`** for virtual background.

        5. Under **Add to Target**, select your project and click **Add Package**.

           For more information, see [Apple's official documentation](https://help.apple.com/xcode/mac/current/#/devb83d64851).
      </TabsContent>

      <TabsContent value="cocoapods">
        1. Go to the project root directory in Terminal and run `pod init`. A text file named `Podfile` is generated in the project folder.

        2. Open `Podfile` and modify the content as follows. Replace `Your App` with your target name.

           ```ruby
           platform :macos, '10.11'
           target 'Your App' do
             # Replace x.y.z with the specific SDK version number, such as 4.4.0.
             pod 'AgoraRtcEngine_macOS', 'x.y.z'
           end
           ```

           Get the latest version number from the [release notes](/en/realtime-media/video/reference/release-notes).

        3. Run `pod install` in Terminal to install the Video SDK. After successful installation, Terminal shows &#x2A;*Pod installation complete!**.

        4. After successful installation, a file with the suffix `.xcworkspace` is generated in the project folder. Open the file in Xcode for subsequent operations.
      </TabsContent>

      <TabsContent value="manual">
        1. Download the latest version of the SDK from [SDKs download](/en/api-reference/sdks?platform=macos) and extract the contents.
        2. Copy the files in the `libs` folder of the SDK package to your project directory.
        3. Open Xcode and [add the corresponding dynamic library](https://help.apple.com/xcode/mac/current/#/dev51a648b07). Make sure the **Embed** property of the added dynamic library is set to **Embed & Sign**.

           <CalloutContainer type="info">
             <CalloutTitle>
               Information
             </CalloutTitle>

             <CalloutDescription>
               Agora SDK uses `libc++` (LLVM) by default. If you need to use `libstdc++` (GNU), contact `support@agora.io`. The library provided by the SDK is a FAT image that includes simulator and device builds.
             </CalloutDescription>
           </CalloutContainer>
      </TabsContent>
    </Tabs>

    <CalloutContainer type="warning">
      <CalloutTitle>
        Note
      </CalloutTitle>

      <CalloutDescription>
        The [privacy updates for App Store submissions](https://developer.apple.com/news/?id=r1henawx) released by Apple require developers to declare approved reasons for using a set of APIs in their app's privacy manifest. Agora provides a [`PrivacyInfo.xcprivacy`](https://download.agora.io/sdk/release/PrivacyInfo.xcprivacy) file that you can include in your project.
      </CalloutDescription>
    </CalloutContainer>

    ## Implement Video Calling [#implement-video-calling-2]

    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">
        ![Video Calling quick start sequence](https://assets-docs.agora.io/images/video-sdk/quick-start-sequence-macos.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.

    ### Import Agora framework [#import-agora-framework-1]

    Add the following import to your swift file:

    ```swift
    import Cocoa
    import AgoraRtcKit
    ```

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

    Call `sharedEngine(withAppId:delegate:)` to create and initialize an `AgoraRtcEngineKit` instance. Provide your [App ID](/en/realtime-media/video/manage-agora-account#get-the-app-id) and an `AgoraRtcEngineDelegate` implementation to [handle SDK events](#subscribe-to-video-sdk-events).

    ```swift
    var agoraKit: AgoraRtcEngineKit!
    let appId = "YOUR_AGORA_APP_ID"

    // Initialize the Agora engine
    func initializeAgoraVideoSDK() {
        agoraKit = AgoraRtcEngineKit.sharedEngine(withAppId: appId, delegate: self)
    }
    ```

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

    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](/en/realtime-media/video/build/authenticate-users/deploy-token-server) in your security infrastructure. For the purpose of this guide [Generate a temporary token](/en/realtime-media/video/manage-agora-account#generate-temporary-tokens).

    * **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 `didJoinChannel` callback.

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

    For Video Calling, set the `channelProfile` to `.communication` and the `clientRoleType` to `.broadcaster`.

    ```swift
    let channelName = "demo"    // Replace with your actual channel name
    let token = "<Authentication token>" // Replace with your token

    // Join the channel with specified options
    func joinChannel() {
        let options = AgoraRtcChannelMediaOptions()
        // In video calling, set the channel use-case to communication
        options.channelProfile = .communication
        // Set the user role as broadcaster (default is audience)
        options.clientRoleType = .broadcaster
        // Publish audio captured by microphone
        options.publishMicrophoneTrack = true
        // Publish video captured by camera
        options.publishCameraTrack = true
        // Auto subscribe to all audio streams
        options.autoSubscribeAudio = true
        // Auto subscribe to all video streams
        options.autoSubscribeVideo = true
        // Use a temporary Token to join the channel
        agoraKit.joinChannel(
            byToken: token,
            channelId: channelName,
            uid: 0,
            mediaOptions: options
        )
    }
    ```

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

    The Video SDK provides a delegate for handling channel events. To use it, conform to the `AgoraRtcEngineDelegate` protocol in your class and implement the event methods you want to handle. The following code implements the `didJoinChannel`, `didOfflineOfUid`, and `didJoinedOfUid` callbacks:

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

    ```swift
    // Extension for handling Agora SDK callbacks
    extension ViewController: AgoraRtcEngineDelegate {

        // Triggered when the local user successfully joins a channel
        func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) {
            print("Successfully joined channel: \(channel) with UID: \(uid)")
        }

        // Triggered when a remote user joins the channel
        func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
            setupRemoteVideo(uid: uid, view: remoteView)
        }
        // Triggered when a remote user leaves the channel
        func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {
            setupRemoteVideo(uid: uid, view: nil)
        }
    }
    ```

    To learn about the other SDK events, see [`AgoraRtcEngineDelegate`](https://api-ref.agora.io/en/video-sdk/ios/4.x/documentation/agorartckit/agorartcenginedelegate).

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

    Follow the steps below to set up the video module.

    1. To enable the video module, call `enableVideo`.
    2. To enable local video preview, call `startPreview`.

       ```swift
       // Enable video functionality (audio is enabled by default)
       agoraKit.enableVideo()
       // Enable local video preview
       agoraKit.startPreview()
       ```

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

    Call `setupLocalVideo` to initialize the local view and set the local video display properties.

    ```swift
    // Configures and starts displaying the local video feed
    func setupLocalVideo() {
        let videoCanvas = AgoraRtcVideoCanvas()
        videoCanvas.view = localView
        videoCanvas.uid = 0  // UID 0 is assigned to the local user
        videoCanvas.renderMode = .hidden
        agoraKit.setupLocalVideo(videoCanvas)
    }
    ```

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

    To initialize the remote user view, call `setupRemoteVideo` and set the local display properties for the remote user. Use the `didJoinedOfUid` callback to get the UID of the remote user.

    ```swift
    func setupRemoteVideo(uid: UInt, view: UIView?) {
        let videoCanvas = AgoraRtcVideoCanvas()
        videoCanvas.uid = uid
        videoCanvas.view = view // Assign view for joining, set to nil for leaving
        videoCanvas.renderMode = .hidden
        agoraKit.setupRemoteVideo(videoCanvas)
    }
    ```

    ### Handle permissions [#handle-permissions-2]

    To access the camera and microphone on Video Calling devices, add the required permissions for real-time interaction. Open the `info.plist` file from the project navigation bar, [edit the property list](https://help.apple.com/xcode/mac/current/#/dev3f399a2a6), to add the required permissions. These permissions are optional. However, if you do not add these permissions, you will not be able to use the corresponding devices.

    | Key                                    | Type   | Value                                                                                                   |
    | :------------------------------------- | :----- | :------------------------------------------------------------------------------------------------------ |
    | Privacy - Microphone Usage Description | String | For the purpose of using the microphone. For example, for a call or live interactive streaming session. |
    | Privacy - Camera Usage Description     | String | For the purpose of using the camera. For example, for a call or live interactive streaming session.     |

    <CalloutContainer type="info">
      <CalloutDescription>
        * If your project depends on third-party plugins or libraries, such as a third-party camera library, and the signature of the plug-in or library is inconsistent with the signature of the project, check the **Hardened Runtime** settings. Specifically, review and potentially disable **Runtime Exceptions** and **Library Validation** in the project configuration.
        * For further information, refer to [Preparing your app for distribution](https://developer.apple.com/documentation/xcode/preparing_your_app_for_distribution).
      </CalloutDescription>
    </CalloutContainer>

    Configure your macOS project settings by navigating to **TARGETS > Project Name > Signing & Capabilities**. Enable **App Sandbox and Hardened Runtime**, and add the necessary permissions as follows:

    | Capability       | Category        | Permission                                                      |
    | :--------------- | :-------------- | :-------------------------------------------------------------- |
    | App Sandbox      | Network         | * Incoming Connections (Server)
    * Outgoing Connections (Client) |
    | App Sandbox      | Hardware        | - Camera
    - Audio Input                                          |
    | Hardened Runtime | Resource Access | * Camera
    * Audio Input                                          |

    ### Start and close the app [#start-and-close-the-app-2]

    When the user launches the app, it joins the channel and starts Video Calling. When the user closes the app, it leaves the channel and ends Video Calling.

    1. To start Video Calling, call the following methods:

       ```swift
       // Initialize the Agora engine
       initializeAgoraVideoSDK()
       // Start the local video preview
       setupLocalVideo()
       // Join an Agora channel
       joinChannel()
       ```

    2. To leave the channel and release SDK resources when the app is closed, call the following methods:

       ```swift
       // Stop local video preview
       agoraKit.stopPreview()
       // Leave the channel and release session-related resources
       agoraKit.leaveChannel(nil)
       // Release all resources used by the Agora SDK
       AgoraRtcEngineKit.destroy()
       ```

    <CalloutContainer type="warning">
      <CalloutTitle>
        caution
      </CalloutTitle>

      <CalloutDescription>
        After destroying the engine, you can no longer use SDK methods and callbacks. To use the real-time interaction functions again, create a new engine. See [Initialize the engine](#initialize-the-engine) for details.
      </CalloutDescription>
    </CalloutContainer>

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

    A complete code sample demonstrating the basic process of real-time interaction is provided for your reference. Copy the following code into your `ViewController.swift` file:

    <Accordions>
      <Accordion title="Complete sample code for real-time Video Calling">
        ```swift
        import Cocoa
        import AgoraRtcKit

        // ViewController.swift

        class ViewController: NSViewController {

            let appId = "<Your app ID>" // Replace with your actual App ID
            let channelName = "demo"    // Replace with your actual channel name
            let token = "<Authentication token>" // Replace with your token

            // UI view for displaying the local video stream
            var localView: NSView!
            // UI view for displaying the remote video stream
            var remoteView: NSView!
            // Instance of the Agora RTC engine
            var agoraKit: AgoraRtcEngineKit!

            override func viewDidLoad() {
                super.viewDidLoad()

                // Initialize the Agora engine
                initializeAgoraVideoSDK()
                // Set up the user interface
                setupUI()
                // Start the local video preview
                setupLocalVideo()
                // Join an Agora channel
                joinChannel()
            }

            // Clean up resources when the view controller is deallocated
            deinit {
                agoraKit.stopPreview()
                agoraKit.leaveChannel(nil)
                AgoraRtcEngineKit.destroy()
            }

            // Initializes the Video SDK instance
            func initializeAgoraVideoSDK() {
                // Create an instance of AgoraRtcEngineKit and set the delegate
                agoraKit = AgoraRtcEngineKit.sharedEngine(withAppId: appId, delegate: self)
            }

            func setupUI() {
                guard let screenFrame = NSScreen.main?.frame else { return } // Ensure screen is available

                // Create the local video view covering the full screen
                localView = NSView(frame: screenFrame)

                // Create the remote video view positioned in the top-right corner
                remoteView = NSView(frame: CGRect(x: self.view.bounds.width - 135, y: 50, width: 250, height: 250))

                // Add video views to the main view
                self.view.addSubview(localView)
                self.view.addSubview(remoteView)
            }


            // Configures and starts displaying the local video feed
            func setupLocalVideo() {
                // Enable video functionality (audio is enabled by default)
                agoraKit.enableVideo()
                let videoCanvas = AgoraRtcVideoCanvas()
                videoCanvas.view = localView
                videoCanvas.uid = 0  // UID 0 is assigned to the local user
                videoCanvas.renderMode = .hidden
                agoraKit.setupLocalVideo(videoCanvas)
                agoraKit.startPreview()
            }

            // Join the channel with specified options
            func joinChannel() {
                let options = AgoraRtcChannelMediaOptions()
                // In video calling, set the channel use-case to communication
                options.channelProfile = .communication
                // Set the user role as broadcaster (default is audience)
                options.clientRoleType = .broadcaster
                // Publish audio captured by microphone
                options.publishMicrophoneTrack = true
                // Publish video captured by camera
                options.publishCameraTrack = true
                // Auto subscribe to all audio streams
                options.autoSubscribeAudio = true
                // Auto subscribe to all video streams
                options.autoSubscribeVideo = true
                // If you set uid=0, the engine generates a uid internally; on success, it triggers didJoinChannel callback
                // Join the channel with a temporary token
                agoraKit.joinChannel(
                    byToken: token,
                    channelId: channelName,
                    uid: 0,
                    mediaOptions: options
                )
            }

            func setupRemoteVideo(uid: UInt, view: NSView?) {
                let videoCanvas = AgoraRtcVideoCanvas()
                videoCanvas.uid = uid
                videoCanvas.view = view // Assign view for joining, set to nil for leaving
                videoCanvas.renderMode = .hidden
                agoraKit.setupRemoteVideo(videoCanvas)
            }
        }

        // Extension for handling Agora SDK callbacks
        extension ViewController: AgoraRtcEngineDelegate {

            // Triggered when the local user successfully joins a channel
            func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) {
                print("Successfully joined channel: \\(channel) with UID: \\(uid)")
            }

            // Triggered when a remote user joins the channel
            func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
                // Assign the remote user’s video stream to the remote view
                setupRemoteVideo(uid: uid, view: remoteView)
            }

            // Triggered when a remote user leaves the channel
            func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {
                // Remove the remote video feed when the user disconnects
                setupRemoteVideo(uid: uid, view: nil)
            }
        }
        ```
      </Accordion>
    </Accordions>

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

    To connect the sample code to your existing UI, ensure that your `ViewController.swift` file includes the `UIView`s used to [Display the local video](#display-the-local-video) and [Display remote video](#display-remote-video).

    Alternatively, use the following sample code to generate a basic user interface. To use this interface, replace the contents of the `ViewController.swift` file with the following code:

    ### Sample code to create the user interface [#sample-code-to-create-the-user-interface-2]

    ```swift
    import Cocoa
    import AgoraRtcKit

    // ViewController.swift

    class ViewController: NSViewController {

        // UI view for displaying the local video stream
        var localView: NSView!
        // UI view for displaying the remote video stream
        var remoteView: NSView!

        override func viewDidLoad() {
            super.viewDidLoad()

            // Set up the user interface
            setupUI()
        }

        func setupUI() {
            guard let screenFrame = NSScreen.main?.frame else { return } // Ensure screen is available

            // Create the local video view covering the full screen
            localView = NSView(frame: screenFrame)

            // Create the remote video view positioned in the top-right corner
            remoteView = NSView(frame: CGRect(x: self.view.bounds.width - 135, y: 50, width: 250, height: 250))

            // Add video views to the main view
            self.view.addSubview(localView)
            self.view.addSubview(remoteView)
        }
    }
    ```

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

    Take the following steps to test the sample code:

    1. In your code update the `appId` and `token`, with the app ID and temporary token you obtained from Agora Console. Use the same `channelName` you filled in when generating the temporary token.

    2. Click **Build** to run your project and wait a few seconds for the app installation to complete.

    3. Allow the app to access the device's microphone and camera.

    4. On a second Video Calling device, repeat the previous steps to install and launch the client. Alternatively, use the [Web demo](https://webdemo.agora.io/basicVideoCall/index.html) to join the same channel and test the following use-cases:

       * If users on both devices join the channel as hosts, they can see and hear each other.
       * If one user joins as host and the other as audience, the host can see themselves in the local video window; the audience can see the host in the remote video window and hear the host.

    ## Reference [#reference-2]

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

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

    ### Next steps [#next-steps-2]

    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](/en/realtime-media/video/build/authenticate-users/authentication-workflow).

    ### Sample project [#sample-project-2]

    Agora provides open source sample projects on [GitHub](https://github.com/AgoraIO/API-Examples) for your reference. Download or view the [JoinChannelVideo](https://github.com/AgoraIO/API-Examples/tree/main/macOS/APIExample/Examples/Basic/JoinChannelVideo) project for a more detailed example.

    ### API reference [#api-reference-2]

    * [`sharedEngine`](https://api-ref.agora.io/en/video-sdk/macos/4.x/documentation/agorartckit/agorartcenginekit/sharedengine\(withappid\:delegate:\))
    * [`joinChannel`](https://api-ref.agora.io/en/video-sdk/macos/4.x/documentation/agorartckit/agorartcenginekit/joinchannel\(bytoken\:channelid\:uid\:mediaoptions\:joinsuccess:\))
    * [`enableVideo`](https://api-ref.agora.io/en/video-sdk/macos/4.x/documentation/agorartckit/agorartcenginekit/enablevideo\(\))
    * [`startPreview`](https://api-ref.agora.io/en/video-sdk/macos/4.x/documentation/agorartckit/agorartcenginekit/startpreview\(\))
    * [`leaveChannel`](https://api-ref.agora.io/en/video-sdk/macos/4.x/documentation/agorartckit/agorartcenginekit/leavechannel\(_:\))
    * [`AgoraRtcEngineDelegate`](https://api-ref.agora.io/en/video-sdk/macos/4.x/documentation/agorartckit/agorartcenginedelegate)

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

    * [How can I fix black screen issues?](/en/api-reference/faq/quality/video_blank#black-screen-on-the-local-side)
    * [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)
    * [How can I troubleshoot the issue of no sound?](/en/api-reference/faq/quality/audio_noaudio)

    ### See also [#see-also-2]

    * [SDK error codes](/en/realtime-media/video/reference/error-codes)
    * [Connection status management](/en/realtime-media/video/build/manage-connection-and-quality/connection-status-management)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="web">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />

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

    <Cards>
      <Card title="RTC SDK API examples" href="https://github.com/AgoraIO/API-Examples-Web" description="Sample projects for the Agora RTC Web SDK 4.x, covering basic and advanced examples for Vue and React." />
    </Cards>

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

    To start a Video Calling session, implement the following steps in your app:

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

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

    * **Send and receive audio and video**: All users can publish streams to the channel and subscribe to audio and video streams published by other users in the channel.

    ![Video calling workflow](https://assets-docs.agora.io/images/video-sdk/video-call.svg)

    ## Prerequisites [#prerequisites-3]

    * A camera and a microphone.
    * A valid Agora account and project. See [Agora account management](/en/realtime-media/video/manage-agora-account) for details.
    * A [supported browser](/en/realtime-media/video/reference/supported-platforms#mobile-browsers).
      Agora strongly recommends using the latest stable version of Google Chrome.
    * [Node.js and npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)

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

    <Tabs defaultValue="new">
      <TabsList>
        <TabsTrigger value="new">
          Create a new project
        </TabsTrigger>

        <TabsTrigger value="existing">
          Add to an existing project
        </TabsTrigger>
      </TabsList>

      <TabsContent value="new">
        To initialize a new Vite project, take the following steps:

        1. Open a terminal and run the following command:

           ```
           npm create vite@latest agora_web_quickstart -- --template vanilla
           ```

           This creates a new folder named `agora_web_quickstart` and initializes a Vite project inside it using the `vanilla` JavaScript template.

        2. Navigate to the newly created folder. Download and set up dependencies for your project:

           ```
           cd agora_web_quickstart
           npm install
           ```

        3. Create a user interface for your project. The UI consists of buttons to join and leave a channel. Refer to [Create a user interface](#create-a-user-interface) to get a bare bones html layout.
      </TabsContent>

      <TabsContent value="existing">
        To add Video Calling to your existing project, take the following steps:

        1. Create a JavaScript file in your project's `src` folder to add `AgoraRTCClient` code that implements specific application logic.

        2. Add an HTML file to your project to create a user interface. The UI consists of buttons to join and leave a channel. Refer to [Create a user interface](#create-a-user-interface) to get a bare bones HTML layout.

        3. Include the JavaScript file in your HTML file.

           ```html
           <script type="module" src="/src/main.js"></script>
           ```
      </TabsContent>
    </Tabs>

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

    Add the Video SDK to your project:

    ```bash
    npm install agora-rtc-sdk-ng
    ```

    ## Implement Video Calling [#implement-video-calling-3]

    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">
        ![Video Calling quick start sequence](https://assets-docs.agora.io/images/video-sdk/quick-start-sequence-web.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.

    ### Import the `AgoraRTC` SDK [#import-the-agorartc-sdk]

    ```js
    import AgoraRTC from "agora-rtc-sdk-ng";
    ```

    ### Initialize an instance of `AgoraRTCClient` [#initialize-an-instance-of-agorartcclient]

    Call [`createClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createclient) to initialize an `AgoraRTCClient` object.
    Set the [Channel mode](#channel-modes) and [Video encoding format](#video-encoding-formats) based on your use case.

    For Video Calling Set `mode` to `rtc`.

    ```javascript
    // RTC client instance
    let client = null;

    // Initialize the AgoraRTC client
    function initializeClient() {
        client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
        setupEventListeners();
    }
    ```

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

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

    * **App ID**: The [App ID](/en/realtime-media/video/manage-agora-account#get-the-app-id) for your project.

    * **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](/en/realtime-media/video/build/authenticate-users/deploy-token-server) in your security infrastructure. For the purpose of this guide [Generate a temporary token](/en/realtime-media/video/manage-agora-account#generate-temporary-tokens).

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

    ```javascript
    // Join a channel and publish local media
    async function joinChannel() {
        await client.join(appId, channel, token, uid);
        await createLocalMediaTracks();
        displayLocalVideo();
        publishLocalTracks();
    }
    ```

    ### Create local media tracks [#create-local-media-tracks]

    To set up the necessary local media tracks:

    * Call `createMicrophoneAudioTrack` to create a local audio track.
    * Call `createCameraVideoTrack` to create a local Video track.

    ```javascript
    // Declare variables for local tracks
    let localAudioTrack = null;
    let localVideoTrack = null;

    // Create local audio and video tracks
    async function createLocalMediaTracks() {
        localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
        localVideoTrack = await AgoraRTC.createCameraVideoTrack();
    }
    ```

    ### Publish local media tracks [#publish-local-media-tracks]

    To make the created audio and video tracks available for other users in the channel, use the `publish` method.

    ```javascript
    async function publishLocalTracks() {
        await client.publish([localAudioTrack, localVideoTrack]);
    }
    ```

    See [Local audio and video tracks](#local-audio-and-video-tracks) to learn more about local tracks.

    ### Set up event listeners [#set-up-event-listeners]

    Use the `on` method to register event listeners for SDK events. The SDK triggers the `user-published` event when a user publishes an audio track in the channel. Similarly, it triggers the `user-unpublished` event when a user leaves the channel, goes offline, or unpublishes a media track.

    ```javascript
    // Handle client events
    function setupEventListeners() {
        // Declare event handler for "user-published"
        client.on("user-published", async (user, mediaType) => {
            // Subscribe to media streams
            await client.subscribe(user, mediaType);
            if (mediaType === "video") {
                // Specify the ID of the DOM element or pass a DOM object.
                user.videoTrack.play("<Specify a DOM element>");
            }
            if (mediaType === "audio") {
                user.audioTrack.play();
            }
        });

        // Handle the "user-unpublished" event to unsubscribe from the user's media tracks
        client.on("user-unpublished", async (user) => {
            const remotePlayerContainer = document.getElementById(user.uid);
            remotePlayerContainer && remotePlayerContainer.remove();
        });
    }
    ```

    * After successfully unsubscribing, the SDK releases the corresponding `RemoteTrack` object. This automatically removes the video playback element and stops audio playback.
    * If a remote user actively stops publishing, the local user receives the `user-unpublished` callback. Upon receiving this callback, the SDK automatically releases the corresponding `RemoteTrack` object, so you do not need to call `unsubscribe` again.
    * The `unsubscribe` method is asynchronous and should be used with `Promise` or `async/await`.

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

    For more information about other `AgoraRTCClient` events, refer to the [API reference](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html#event_channel_media_relay_event).

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

    To play the local video, use the `play` method of the local video track. Pass an element ID or a DOM element from your UI where you want to render the video.

    ```javascript
    // Display local video
    function displayLocalVideo() {
        const localPlayerContainer = document.createElement("div");
        localPlayerContainer.id = uid;
        localPlayerContainer.textContent = `Local user ${uid}`;
        localPlayerContainer.style.width = "640px";
        localPlayerContainer.style.height = "480px";
        document.body.append(localPlayerContainer);
        localVideoTrack.play(localPlayerContainer);
    }
    ```

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

    To display the remote video, call the `play` method of the remote user's `videoTrack` and pass in either the element ID or a DOM element from your UI where you want to render the video.

    ```js
    // Display remote user's video
    function displayRemoteVideo(user) {
        const remotePlayerContainer = document.createElement("div");
        remotePlayerContainer.id = user.uid.toString();
        remotePlayerContainer.textContent = `Remote user ${user.uid}`;
        remotePlayerContainer.style.width = "640px";
        remotePlayerContainer.style.height = "480px";
        document.body.append(remotePlayerContainer);
        user.videoTrack.play(remotePlayerContainer);
    }
    ```

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

    To exit the channel, close local audio and video tracks and call `leave`.

    ```javascript
    // Leave the channel and clean up
    async function leaveChannel() {
        // Stop the local media tracks to release the microphone and camera resources
        if (localAudioTrack) {
            localAudioTrack.close();
            localAudioTrack = null;
        }
        if (localVideoTrack) {
            localVideoTrack.close();
            localVideoTrack = null;
        }
        // Leave the channel
        await client.leave();
    }
    ```

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

    A complete code sample demonstrating the basic process of real-time interaction is provided here for your reference. To use the sample, copy the following code to the JavaScript file in the project's `src` folder.

    <Accordions>
      <Accordion title="Complete sample code for real-time Video Calling">
        ```javascript
        import AgoraRTC from "agora-rtc-sdk-ng";

        // RTC client instance
        let client = null;

        // Declare variables for the local tracks
        let localAudioTrack = null;
        let localVideoTrack = null;

        // Connection parameters
        let appId = "<-- Insert app ID -->";
        let channel = "<-- Insert channel name -->";
        let token = "<-- Insert token -->";
        let uid = 0; // User ID

        // Initialize the AgoraRTC client
        function initializeClient() {
            client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
            setupEventListeners();
        }

        // Handle client events
        function setupEventListeners() {
            client.on("user-published", async (user, mediaType) => {
                await client.subscribe(user, mediaType);
                console.log("subscribe success");

                if (mediaType === "video") {
                    displayRemoteVideo(user);
                }

                if (mediaType === "audio") {
                    user.audioTrack.play();
                }
            });

            client.on("user-unpublished", (user) => {
                const remotePlayerContainer = document.getElementById(user.uid);
                remotePlayerContainer && remotePlayerContainer.remove();
            });
        }

        // Join a channel and publish local media
        async function joinChannel() {
            await client.join(appId, channel, token, uid);
            await createLocalTracks();
            await publishLocalTracks();
            displayLocalVideo();
            console.log("Publish success!");
        }

        // Create local audio and video tracks
        async function createLocalTracks() {
            localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
            localVideoTrack = await AgoraRTC.createCameraVideoTrack();
        }

        // Publish local audio and video tracks
        async function publishLocalTracks() {
            await client.publish([localAudioTrack, localVideoTrack]);
        }

        // Display local video
        function displayLocalVideo() {
            const localPlayerContainer = document.createElement("div");
            localPlayerContainer.id = uid;
            localPlayerContainer.textContent = `Local user ${uid}`;
            localPlayerContainer.style.width = "640px";
            localPlayerContainer.style.height = "480px";
            document.body.append(localPlayerContainer);
            localVideoTrack.play(localPlayerContainer);
        }

        // Display remote video
        function displayRemoteVideo(user) {
            const remoteVideoTrack = user.videoTrack;
            const remotePlayerContainer = document.createElement("div");
            remotePlayerContainer.id = user.uid.toString();
            remotePlayerContainer.textContent = `Remote user ${user.uid}`;
            remotePlayerContainer.style.width = "640px";
            remotePlayerContainer.style.height = "480px";
            document.body.append(remotePlayerContainer);
            remoteVideoTrack.play(remotePlayerContainer);
        }

        // Leave the channel and clean up
        async function leaveChannel() {
            // Close local tracks
            localAudioTrack.close();
            localVideoTrack.close();

            // Remove local video container
            const localPlayerContainer = document.getElementById(uid);
            localPlayerContainer && localPlayerContainer.remove();

            // Remove all remote video containers
            client.remoteUsers.forEach((user) => {
                const playerContainer = document.getElementById(user.uid);
                playerContainer && playerContainer.remove();
            });

            // Leave the channel
            await client.leave();
        }

        // Set up button click handlers
        function setupButtonHandlers() {
            document.getElementById("join").onclick = joinChannel;
            document.getElementById("leave").onclick = leaveChannel;
        }

        // Start the basic call
        function startBasicCall() {
            initializeClient();
            window.onload = setupButtonHandlers;
        }

        startBasicCall();
        ```
      </Accordion>
    </Accordions>

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

    Open `index.html` in the project's root folder and replace the contents with the following code to implement a basic client user interface:

    ### Sample code to create the user interface [#sample-code-to-create-the-user-interface-3]

    ```html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Web SDK Video Quickstart</title>
    </head>
    <body>
        <h2 class="left-align">Web SDK Video Quickstart</h2>
        <div class="row">
            <div>
                <button type="button" id="join">Join</button>
                <button type="button" id="leave">Leave</button>
            </div>
        </div>
        <!-- Include the JavaScript file -->
        <script type="module" src="/src/main.js"></script>
    </body>
    </html>
    ```

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

    Take the following steps to run and test the sample code:

    1. In your `.js` file, update the values for `appId`, and `token` with values from Agora Console. Use the same `channel` name you used to generate the token.

    2. To start the development server, use the following command:

       ```shell
       npm run dev
       ```

    3. Open your browser and navigate to the URL displayed in your terminal, for example, `http://localhost:5173`.

       You see the following page:

       ![](https://assets-docs.agora.io/images/video-sdk/quickstart-ui-web.png)

    4. On a second device, repeat the previous steps to install and launch the client. Alternatively, use the [Web demo](https://webdemo.agora.io/basicVideoCall/index.html) or clone the [sample project on Github](https://github.com/AgoraIO/API-Examples-Web) to join the same channel and test the following use-cases:

       1. Click **Join** to join the channel.
       2. Enter the same app ID, channel name, and temporary token.

          <CalloutContainer type="info">
            <CalloutTitle>
              Information
            </CalloutTitle>

            <CalloutDescription>
              * Run the web app on a local server (localhost) for testing purposes only. When deploying to a production environment, use the HTTPS protocol.
              * Due to browser security policies that restrict HTTP addresses to 127.0.0.1, the Agora Web SDK only supports HTTPS protocol and `http://localhost` (`http://127.0.0.1`). Please do not use the HTTP protocol to access your project, except for `http://localhost` (`http://127.0.0.1`).
            </CalloutDescription>
          </CalloutContainer>

    ## Reference [#reference-3]

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

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

    ### Next steps [#next-steps-3]

    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](/en/realtime-media/video/build/authenticate-users/authentication-workflow).

    ### Sample project [#sample-project-3]

    * [Build a NextJS Video Call App](https://www.agora.io/en/blog/build-a-next-js-video-call-app/)

    * Agora provides open source sample projects on [GitHub](https://github.com/AgoraIO/API-Examples-Web) for your reference. Download or view the [basicVideoCall](https://github.com/AgoraIO/API-Examples-Web/tree/main/src/example/basic/basicVideoCall) project for a more detailed example.

    ### API reference [#api-reference-3]

    * [`AgoraRTC.createClient`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createclient)
    * [`IAgoraRTCRemoteUser`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcremoteuser.html)
    * [`play`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iremoteaudiotrack.html#play)
    * [`join`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html#join)
    * [`createMicrophoneAudioTrack`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createmicrophoneaudiotrack)
    * [`createCameraVideoTrack`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartc.html#createcameravideotrack)
    * [`publish`](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html#publish)
    * [AgoraRTCClient events](https://api-ref.agora.io/en/video-sdk/web/4.x/interfaces/iagorartcclient.html#event_channel_media_relay_event)

    ### Channel modes [#channel-modes]

    The Video SDK supports the following channel modes:

    * `rtc`: Communication use-case
    * `live`: Live broadcast use-case

    #### Communication use-case [#communication-use-case]

    It is suitable for use-cases where all users in the channel need to communicate with each other and the total number of users is not too large, such as multi-person conferences and online chats.

    #### Live broadcast use-case [#live-broadcast-use-case]

    It is suitable for use-cases where there are few publishers but many subscribers. In this use-case, the SDK defines two user roles: audience (default) and host. Hosts can send and receive audio and video, but audience cannot send and may only receive audio and video. You can specify user roles by setting the parameters `createClient` of role, or you can call `setClientRole` to dynamically modify user roles.

    ### Video encoding formats [#video-encoding-formats]

    You can set the `codec` parameter in the `createClient` method to the following video encoding formats:

    * `vp8` (VP8)
    * `h264` (H.264)
    * `vp9` (VP9)

    This setting only affects the video encoding format of the host. For the audience, as long as their device and browser support the decoding of this format, the subscription can be completed normally.

    Support for these formats may vary across browsers and devices. The following table lists the `codec` formats supported by different browsers for reference:

    | Browser            | VP8 | H.264                | VP9                     |
    | :----------------- | --- | -------------------- | ----------------------- |
    | Desktop Chrome 58+ | ✔   | ✔                    | ✔                       |
    | Firefox 56+        | ✔   | ✔                    | ✔(Requires Firefox 69+) |
    | Safari 12.1+       | ✔   | ✔                    | ✔(Requires Safari 16+)  |
    | Safari \< 12.1     | ✘   | ✔                    | ✘                       |
    | AndroidChrome 58+  | ✔   | No clear information | ✔(Requires Chrome 68+)  |

    <CalloutContainer type="info">
      <CalloutDescription>
        * H.264 support on Firefox depends on the `OpenH264` video codec plug-in from Cisco Systems, Inc.
        * For Chrome, support for H.264 on Android devices varies based on device hardware compatibility with hardware codecs mandated by Chrome.
      </CalloutDescription>
    </CalloutContainer>

    ### Local audio and video tracks [#local-audio-and-video-tracks]

    The SDK uses a hierarchy where all local track objects derive from the `LocalTrack` base class. This class defines the common behavior for all local tracks. Specific track types, such as `LocalAudioTrack` and `LocalVideoTrack` inherit from `LocalTrack` and extend its functionality.

    To publish a local track, you call the `publish` method of the client with the `LocalTrack` object as an input parameter. This approach makes publishing a track independent of how you create your local track.

    There are two main types of local tracks: `LocalAudioTrack` and `LocalVideoTrack` for publishing audio and video, respectively. Each type of `LocalTrack` comes with its own set of tools. For example, `LocalAudioTrack` lets you control the volume, while `LocalVideoTrack` has functions for customizing video.

    The SDK includes more specific classes based on `LocalAudioTrack` and `LocalVideoTrack`. For example, the `CameraVideoTrack`, is a type of `LocalVideoTrack` that you can use to publish video from your camera. It comes with extra features for controlling the camera and adjusting video quality.

    The following diagram shows the relationship between the `LocalTrack` classes:

    ![ILocalTrack](https://assets-docs.agora.io/images/video-sdk/ILocalTrack-web.png)

    ### Other integration methods [#other-integration-methods]

    When you use `npm` to install the Web SDK, you can enable tree shaking to reduce the size of the app after integration. For details, see [Using tree shaking](/en/realtime-media/video/build/optimize-and-operate/app-size-optimization#use-tree-shaking).

    In addition to using `npm` to install the Web SDK, you can also use the following methods:

    * In the project HTML file, add the following tag to obtain the SDK from CDN:

      ```html
      <script src="https://download.agora.io/sdk/release/AgoraRTC_N-4.24.5.js"></script>
      ```

    * Download the Video SDK package locally, save the `.js` files in the SDK package to the project directory, and then add the following tag to the project HTML file:

      ```html
      <script src="./AgoraRTC_N-4.24.5.js"></script>
      ```

    Visit the [Download](/en/api-reference/sdks?platform=web) page to obtain the link for the latest SDK version.

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

    **Why do I get a `digital envelope routines::unsupported` error when running the quickstart project locally?**

    This issue arises in projects configured with `webpack` for local execution due to changes in Node.js 16 and above. The modifications in Node.js, particularly its dependency on OpenSSL (detailed in the [node issue](https://github.com/nodejs/node/issues/29817)), impact the local development environment dependencies of the project. Refer to the [webpack issue](https://github.com/webpack/webpack/issues/14532) for details.

    Use one of the following solutions to resolve the issue:

    * Run the following command to set a temporary environment variable (Recommended):

      ```shell
      export NODE_OPTIONS=--openssl-legacy-provider
      ```

    * Temporarily switch to a lower version of Node.js.

    ### See also [#see-also-3]

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

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="windows">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />

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

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

    To start a Video Calling session, implement the following steps in your app:

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

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

    * **Send and receive audio and video**: All users can publish streams to the channel and subscribe to audio and video streams published by other users in the channel.

    ![Video calling workflow](https://assets-docs.agora.io/images/video-sdk/video-call.svg)

    ## Prerequisites [#prerequisites-4]

    * A camera and a microphone.
    * A valid Agora account and project. See [Agora account management](/en/realtime-media/video/manage-agora-account) for details.
    * A device running Windows 7 or higher.
    * Microsoft Visual Studio 2017 or higher with [C++ desktop development](https://devblogs.microsoft.com/cppblog/windows-desktop-development-with-c-in-visual-studio/) support and C++ 11 or above.
    * If you use C# for development, you also need the [.NET Framework](https://learn.microsoft.com/en-us/dotnet/framework/install/guide-for-developers).

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

    <Tabs defaultValue="new">
      <TabsList>
        <TabsTrigger value="new">
          Create a new project
        </TabsTrigger>

        <TabsTrigger value="existing">
          Add to an existing project
        </TabsTrigger>
      </TabsList>

      <TabsContent value="new">
        The following steps outline the process to set up a new Visual Studio 2022 project on Windows 11 for implementing real-time audio and video interaction functions.

        1. In Visual Studio, select **File > New > Project** to create a new project. In the pop-up window, select **MFC application** as the project template, click **Next**, update the project name to `AgoraQuickStart`, set the project storage location, and then click **Create**.

        2. In the pop-up MFC application window, set the application type to **Dialog-based** , and set **Use MFC** to **Use MFC in a shared DLL**. Enter the generated class, set the generated class to **Dlg**, set the base class to **CDialog**, and click **Finish**.
      </TabsContent>

      <TabsContent value="existing">
        To integrate real-time audio and video interaction into your project:

        1. Launch Visual Studio 2022 and open your existing project by selecting **File > Open > Project/Solution**.

        2. Navigate to your project directory and open the `.sln` file.
      </TabsContent>
    </Tabs>

    3. Create a user-interface for your app based on your application use-case.

       A basic UI consists of the following controls:

       * A Picture Control for displaying local video
       * A Picture Control for displaying remote video
       * An Edit Control for entering a channel name
       * Buttons to join and leave a channel

       Refer to [Create a user interface](#create-a-user-interface) to get a bare bones sample layout.

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

    Install the Agora Video SDK:

    1. Download the latest Windows [SDK](/en/api-reference/sdks).

    2. Unzip and open the downloaded SDK. Copy all subfolders in `sdk/` to your solution folder. Make sure these subfolders are in the same directory as your `.sln` file.

    #### Configure the project [#configure-the-project]

    In the Solution Explorer window, right-click the project name and click **Properties** to configure the following:

    1. Go to the **C/C++ > General > Additional Include Directory** menu, click **Edit**, and in the pop-up window enter `$(SolutionDir)sdk\high_level_api\include`.
    2. Go to the **Linker > General > Additional Library Directory** menu, click **Edit**, and in the pop-up window:
       * for 64 bit Windows, enter `$(SolutionDir)sdk\x86_64`.
       * for x86 Windows, enter `$(SolutionDir)sdk\x86`.
    3. Go to the **Linker > Input > Additional Dependencies** menu, click **Edit**, and in the pop-up window:
       * for 64 bit Windows, enter `$(SolutionDir)sdk\x86_64\agora_rtc_sdk.dll.lib`.
       * for x86 Windows, enter `$(SolutionDir)sdk\x86\agora_rtc_sdk.dll.lib`.
    4. Enter the **Advanced** menu, and in **Advanced Properties**, set **Copy contents to OutDir** and **Copy C++ runtime to output directory** to `Yes`.
    5. Go to the **Build Events > Post-Build Events > Command Line** menu and enter `copy $(SolutionDir)sdk\x86_64\*.dll $(SolutionDir)$(Platform)\$(Configuration)`.
    6. Click **Apply** to save the configuration.

    ## Implement Video Calling [#implement-video-calling-4]

    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">
        ![Video Calling 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.

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

    For real-time communication, initialize an `IRtcEngine` instance and set up event handlers to manage user interactions within the channel. Use `RtcEngineContext` to specify the [App ID](/en/realtime-media/video/manage-agora-account#get-the-app-id), and custom [event handler](#subscribe-to-video-sdk-events), then call `initialize` to initialize the engine, enabling further channel operations. Add the following code to your header and `.cpp` files:

    ```cpp
    // Declare the required variables
    IRtcEngine* m_rtcEngine = nullptr; // RTC engine instance
    CAgoraQuickStartRtcEngineEventHandler m_eventHandler; // IRtcEngineEventHandler
    ```

    ```cpp
    void CAgoraQuickStartDlg::initializeAgoraEngine() {
        // Create IRtcEngine object
        m_rtcEngine = createAgoraRtcEngine();
        // Create IRtcEngine context object
        RtcEngineContext context;
        // Input your App ID. You can obtain your project's App ID from the Agora Console
        context.appId = APP_ID;
        // Add event handler for callbacks and events
        context.eventHandler = &m_eventHandler;
        // Initialize
        int ret = m_rtcEngine->initialize(context);
        m_initialize = (ret == 0);

        if (m_initialize) {
            // Enable the video module
            m_rtcEngine->enableVideo();
        } else {
            AfxMessageBox(_T("Failed to initialize Agora RTC engine"));
        }
    }
    ```

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

    To join a channel, call `joinChannel`\[2/2] 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](/en/realtime-media/video/build/authenticate-users/deploy-token-server) in your security infrastructure. For the purpose of this guide [Generate a temporary token](/en/realtime-media/video/manage-agora-account#generate-temporary-tokens).

    * **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 Video Calling, set the `channelProfile` to `CHANNEL_PROFILE_COMMUNICATION` and the `clientRoleType` to `CLIENT_ROLE_BROADCASTER`.

    ```cpp
    void CAgoraQuickStartDlg::joinChannel(const char* token, const char* channelName) {
        ChannelMediaOptions options;
        // Set the channel profile to live broadcasting
        options.channelProfile = CHANNEL_PROFILE_COMMUNICATION;
        // Set the user role to broadcaster; to set the user role as audience, keep the default value
        options.clientRoleType = CLIENT_ROLE_BROADCASTER;
        // Publish the audio stream captured by the microphone
        options.publishMicrophoneTrack = true;
        // Publish the camera track
        options.publishCameraTrack = true;
        // Automatically subscribe to all audio streams
        options.autoSubscribeAudio = true;
        // Automatically subscribe to all video streams
        options.autoSubscribeVideo = true;
        // Join the channel using the temporary token obtained from the console
        m_rtcEngine->joinChannel(token, channelName, 0, options);
    }
    ```

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

    The Video SDK provides an interface for subscribing to channel events. To use it, create a custom event handler class by inheriting from `IRtcEngineEventHandler` and override its methods to handle real-time interaction events. Add callbacks to receive notification of users joining and leaving the channel.

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

    ```cpp
    // Define the CAgoraQuickStartRtcEngineEventHandler class to handle callback events such as users joining and leaving the channel
    class CAgoraQuickStartRtcEngineEventHandler
        : public IRtcEngineEventHandler {
        public:
            CAgoraQuickStartRtcEngineEventHandler()
                : m_hMsgHandler(nullptr) {
            }

            // Set the handle of the message receiving window
            void SetMsgReceiver(HWND hWnd) {
                m_hMsgHandler = hWnd;
            }

            // Register onJoinChannelSuccess callback
            // This callback is triggered when a local user successfully joins a channel
            virtual void onJoinChannelSuccess(const char* channel, uid_t uid, int elapsed) {
                if (m_hMsgHandler) {
                    ::PostMessage(m_hMsgHandler, WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), uid, 0);
                }
            }

            // Register onUserJoined callback
            // This callback is triggered when the remote host successfully joins the channel
            virtual void onUserJoined(uid_t uid, int elapsed) {
                if (m_hMsgHandler) {
                    ::PostMessage(m_hMsgHandler, WM_MSGID(EID_USER_JOINED), uid, 0);
                }
            }

            // Register onUserOffline callback
            // This callback is triggered when the remote host leaves the channel or is offline
            virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) {
                if (m_hMsgHandler) {
                    ::PostMessage(m_hMsgHandler, WM_MSGID(EID_USER_OFFLINE), uid, 0);
                }
            }

        private:
            HWND m_hMsgHandler;
    };
    ```

    Implement the callback functions.

    ```cpp
    LRESULT CAgoraQuickStartDlg::OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lParam) {
        // Join channel success callback
        uid_t localUid = wParam;
        return 0;
    }

    LRESULT CAgoraQuickStartDlg::OnEIDUserJoined(WPARAM wParam, LPARAM lParam) {
        // Remote user joined callback
        uid_t remoteUid = wParam;
        if (m_remoteRender) {
            return 0;
        }
        // Render remote view
        VideoCanvas canvas;
        canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
        canvas.uid = remoteUid;
        canvas.view = m_staRemote.GetSafeHwnd();
        m_rtcEngine->setupRemoteVideo(canvas);
        m_remoteRender = true;
        return 0;
    }

    LRESULT CAgoraQuickStartDlg::OnEIDUserOffline(WPARAM wParam, LPARAM lParam) {
        // Remote user left callback
        uid_t remoteUid = wParam;
        if (!m_remoteRender) {
            return 0;
        }
        // Clear remote view
        VideoCanvas canvas;
        canvas.uid = remoteUid;
        m_rtcEngine->setupRemoteVideo(canvas);
        m_remoteRender = false;
        return 0;
    }
    ```

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

    Call the `enableVideo` method to enable the video module.

    ```cpp
    // Enable the video module
    m_rtcEngine->enableVideo();
    ```

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

    Follow these steps to set up and start the local video preview:

    1. Create a `VideoCanvas` instance and configure its properties:
       1. Set the video rendering mode.
       2. Specify the user ID (`uid`).
       3. Define the display window.
    2. Call the `setupLocalVideo` method to apply the `VideoCanvas` configuration.
    3. Call the `startPreview` method to start the local video preview.

       ```cpp
       void CAgoraQuickStartDlg::setupLocalVideo() {
           // Set local video display properties
           VideoCanvas canvas;
           // Set video to be scaled proportionally
           canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
           // User ID
           canvas.uid = 0;
           // Video display window
           canvas.view = m_staLocal.GetSafeHwnd();
           m_rtcEngine->setupLocalVideo(canvas);
           // Preview the local video
           m_rtcEngine->startPreview();
       }
       ```

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

    To display the remote user's video:

    1. Define the video display properties using `VideoCanvas`.
    2. Call `setupRemoteVideo` to render the video.

       ```cpp
       void CAgoraQuickStartDlg::setupRemoteVideo(uid_t remoteUid) {
           // Set remote video display properties
           VideoCanvas canvas;
           // Set video size to be proportionally scaled
           canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
           // Remote user ID
           canvas.uid = remoteUid;
           // You can only choose to set either view or surfaceTexture. If both are set, only the settings in view take effect.
           canvas.view = m_staRemote.GetSafeHwnd();
           m_rtcEngine->setupRemoteVideo(canvas);
           m_remoteRender = true;
           return 0;
       }
       ```

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

    When a user ends a call, or closes the client call `leaveChannel` to exit the current channel.

    To stop the local video preview and clear the view:

    1. Call `stopPreview` to stop playing the local video.
    2. Call `setupLocalVideo`, passing an empty `VideoCanvas` to reset the view.

       ```cpp
       void CAgoraQuickStartDlg::LeaveChannel() {
           if (m_rtcEngine) {
               // Stop local video preview
               m_rtcEngine->stopPreview();
               // Leave the channel
               m_rtcEngine->leaveChannel();
               // Clear local view
               VideoCanvas canvas;
               canvas.uid = 0;
               m_rtcEngine->setupLocalVideo(canvas);
               m_remoteRender = false;
           }
       }
       ```

    ### Release resources [#release-resources]

    To destroy the engine instance, call `release` :

    ```cpp
    // Release resources when the object is destroyed
    m_rtcEngine->release(true);
    m_rtcEngine = NULL;
    ```

    <CalloutContainer type="warning">
      <CalloutTitle>
        Caution
      </CalloutTitle>

      <CalloutDescription>
        After you call `Dispose`, you can no longer use any SDK methods or callbacks. To use real-time Video Calling again, you must create a new engine. For more information, see [Initialize the Engine](#initialize-the-engine).
      </CalloutDescription>
    </CalloutContainer>

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

    A complete code sample that implements the basic process of real-time interaction is presented here for your reference. Copy the sample code into your project to quickly implement the basic functions of real-time interaction.

    ### AgoraQuickStartDlg.h [#agoraquickstartdlgh]

    <Accordions>
      <Accordion title="Complete sample code for real-time Video Calling">
        ```cpp
        #pragma once
        #include
        // Import related header files
        #include
        using namespace now;
        using namespace agora::rtc;
        using namespace agora::media;
        using namespace agora::media::base;

        // Define the message ID
        #define WM_MSGID(code) (WM_USER+0x200+code)
        #define EID_JOIN_CHANNEL_SUCCESS        0x00000002
        #define EID_USER_JOINED        0x00000004
        #define EID_USER_OFFLINE        0x00000004

        // Define the CAgoraQuickStartRtcEngineEventHandler class to handle callback events such as users joining and leaving the channel
        class CAgoraQuickStartRtcEngineEventHandler
            : public IRtcEngineEventHandler {
            public:
                CAgoraQuickStartRtcEngineEventHandler()
                    : m_hMsgHandler(nullptr) {
                }

                // Set the handle of the message receiving window
                void SetMsgReceiver(HWND hWnd) {
                    m_hMsgHandler = hWnd;
                }

                // Register onJoinChannelSuccess callback
                // This callback is triggered when a local user successfully joins a channel
                virtual void onJoinChannelSuccess(const char* channel, uid_t uid, int elapsed) {
                    if (m_hMsgHandler) {
                        ::PostMessage(m_hMsgHandler, WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), uid, 0);
                    }
                }

                // Register onUserJoined callback
                // This callback is triggered when the remote host successfully joins the channel
                virtual void onUserJoined(uid_t uid, int elapsed) {
                    if (m_hMsgHandler) {
                        ::PostMessage(m_hMsgHandler, WM_MSGID(EID_USER_JOINED), uid, 0);
                    }
                }

                // Register onUserOffline callback
                // This callback is triggered when the remote host leaves the channel or is offline
                virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) {
                    if (m_hMsgHandler) {
                        ::PostMessage(m_hMsgHandler, WM_MSGID(EID_USER_OFFLINE), uid, 0);
                    }
                }

            private:
                HWND m_hMsgHandler;
        };

        // CAgoraQuickStartDlg dialog box
        class CAgoraQuickStartDlg : public CDialog
        {
            // Construction
            public:
                CAgoraQuickStartDlg(CWnd* pParent = nullptr); // Standard constructor
                virtual ~CAgoraQuickStartDlg();

                // Dialog box data
            #ifdef AFX_DESIGN_TIME
                enum { IDD = IDD_AGORAQUICKSTART_DIALOG };
            #endif

                // Handle the join/leave button click event
                afx_msg void OnBnClickedBtnJoin();
                afx_msg void OnBnClickedBtnLeave();
                // Handle callback events such as user joining/user leaving
                afx_msg LRESULT OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lParam);
                afx_msg LRESULT OnEIDUserJoined(WPARAM wParam, LPARAM lParam);
                afx_msg LRESULT OnEIDUserOffline(WPARAM wParam, LPARAM lParam);

            protected:
                HICON m_hIcon;

                CEdit m_edtChannelName;

                // DDX/DDV support
                virtual void DoDataExchange(CDataExchange* pDX);
                virtual BOOL OnInitDialog();
                afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
                afx_msg void OnPaint();
                afx_msg HCURSOR OnQueryDragIcon();
                DECLARE_MESSAGE_MAP()

                std::string cs2utf8(CString str);

            private:
                IRtcEngine* m_rtcEngine = nullptr;
                CAgoraQuickStartRtcEngineEventHandler m_eventHandler;
                bool m_initialize = false;
                bool m_remoteRender = false;
        };
        ```

        ### AgoraQuickStartDlg.cpp [#agoraquickstartdlgcpp]

        ```cpp
        #include "pch.h"
        #include "framework.h"
        #include "AgoraQuickStart.h"
        #include "AgoraQuickStartDlg.h"
        #include "afxdialogex.h"
        #ifdef _DEBUG
        #define new DEBUG_NEW
        #endif

        // CAboutDlg dialog used for App "About" menu item
        class CAboutDlg : public CDialogEx {
        public:
            CAboutDlg();
            // Dialog Data
        #ifdef AFX_DESIGN_TIME
            enum {
                IDD = IDD_ABOUTBOX
            };
        #endif
        protected:
            virtual void DoDataExchange(CDataExchange* pDX);
            // DDX/DDV support
            // Implementation
        protected:
            DECLARE_MESSAGE_MAP()
        };

        CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX) {
        }

        void CAboutDlg::DoDataExchange(CDataExchange* pDX) {
            CDialogEx::DoDataExchange(pDX);
        }

        BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
        END_MESSAGE_MAP()

        // CAgoraQuickStartDlg dialog for handling main user interactions and callback events
        CAgoraQuickStartDlg::CAgoraQuickStartDlg(CWnd* pParent
            /*=nullptr*/)
            : CDialog(IDD_AGORAQUICKSTART_DIALOG, pParent) {
            m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
        }

        CAgoraQuickStartDlg::~CAgoraQuickStartDlg() {
            // When deleting the CAgoraQuickStartDlg object, release the engine and related resources
            if (m_rtcEngine) {
                m_rtcEngine->release(true);
                m_rtcEngine = NULL;
            }
        }

        void CAgoraQuickStartDlg::DoDataExchange(CDataExchange* pDX) {
            CDialog::DoDataExchange(pDX);
            // Associate controls and variables for reading and writing data to controls
            DDX_Control(pDX, IDC_EDIT_CHANNEL, m_edtChannelName);
            DDX_Control(pDX, IDC_STATIC_REMOTE, m_staRemote);
            DDX_Control(pDX, IDC_STATIC_LOCAL, m_staLocal);
        }

        BEGIN_MESSAGE_MAP(CAgoraQuickStartDlg, CDialog)
            // Declare message mappings for handling Windows messages and user events such as joining and leaving channels
            ON_WM_SYSCOMMAND()
            ON_WM_PAINT()
            ON_WM_QUERYDRAGICON()
            ON_BN_CLICKED(ID_BTN_JOIN, &CAgoraQuickStartDlg::OnBnClickedBtnJoin)
            ON_BN_CLICKED(ID_BTN_LEAVE, &CAgoraQuickStartDlg::OnBnClickedBtnLeave)
            ON_MESSAGE(WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), CAgoraQuickStartDlg::OnEIDJoinChannelSuccess)
            ON_MESSAGE(WM_MSGID(EID_USER_JOINED), &CAgoraQuickStartDlg::OnEIDUserJoined)
            ON_MESSAGE(WM_MSGID(EID_USER_OFFLINE), &CAgoraQuickStartDlg::OnEIDUserOffline)
        END_MESSAGE_MAP()

        // CAgoraQuickStartDlg message handlers
        // Insert your project's App ID obtained from the Agora Console
        #define APP_ID "<YOUR_APP_ID>"
        // Insert the temporary token obtained from the Agora Console
        #define token "<YOUR_TOKEN>"

        void CAgoraQuickStartDlg::initializeAgoraEngine() {
            m_rtcEngine = createAgoraRtcEngine();
            RtcEngineContext context;
            context.appId = APP_ID;
            context.eventHandler = &m_eventHandler;

            int ret = m_rtcEngine->initialize(context);
            if (ret == 0) {
                // Enable the video module
                m_rtcEngine->enableVideo();
            } else {
                AfxMessageBox(_T("Failed to initialize Agora RTC engine"));
            }
        }

        BOOL CAgoraQuickStartDlg::OnInitDialog() {
            CDialog::OnInitDialog();

            // Add "About..." menu item to the system menu
            CMenu* pSysMenu = GetSystemMenu(FALSE);
            if (pSysMenu != nullptr) {
                CString strAboutMenu;
                strAboutMenu.LoadString(IDS_ABOUTBOX);
                if (!strAboutMenu.IsEmpty()) {
                    pSysMenu->AppendMenu(MF_SEPARATOR);
                    pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
                }
            }

            // Set the dialog icons
            SetIcon(m_hIcon, TRUE);  // Set big icon
            SetIcon(m_hIcon, FALSE); // Set small icon

            // Set the message receiver
            m_eventHandler.SetMsgReceiver(m_hWnd);

            // Initialize Agora engine
            initializeAgoraEngine();

            return TRUE; // Unless focus is set to a control, return TRUE
        }


        void CAgoraQuickStartDlg::OnSysCommand(UINT nID, LPARAM lParam) {
            if ((nID & 0xFFF0) == IDM_ABOUTBOX) {
                CAboutDlg dlgAbout;
                dlgAbout.DoModal();
            } else {
                CDialog::OnSysCommand(nID, lParam);
            }
        }

        // If you add a minimize button to the dialog box, you need the following code
        // to draw the icon. For MFC applications using the document/view model, this is done automatically by the framework
        void CAgoraQuickStartDlg::OnPaint() {
            if (IsIconic()) {
                CPaintDC dc(this);
                // Device context for drawing
                SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
                // Center the icon in the client rectangle
                int cxIcon = GetSystemMetrics(SM_CXICON);
                int cyIcon = GetSystemMetrics(SM_CYICON);
                CRect rect;
                GetClientRect(&rect);
                int x = (rect.Width() - cxIcon + 1) / 2;
                int y = (rect.Height() - cyIcon + 1) / 2;
                // Draw the icon
                dc.DrawIcon(x, y, m_hIcon);
            } else {
                CDialog::OnPaint();
            }
        }

        // This function is called by the framework to obtain the cursor when the user drags the minimized window
        HCURSOR CAgoraQuickStartDlg::OnQueryDragIcon() {
            return static_cast<HCURSOR>(m_hIcon);
        }

        std::string CAgoraQuickStartDlg::cs2utf8(CString str) {
            char szBuf[2 * MAX_PATH] = { 0 };
            WideCharToMultiByte(CP_UTF8, 0, str.GetBuffer(0), str.GetLength(), szBuf, 2 * MAX_PATH, NULL, NULL);
            return szBuf;
        }

        void CAgoraQuickStartDlg::joinChannel(const char* token, const char* channelName) {
            ChannelMediaOptions options;
            // Set the channel profile to live broadcasting
            options.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
            // Set the user role to broadcaster; to set the user role as audience, keep the default value
            options.clientRoleType = CLIENT_ROLE_BROADCASTER;
            // Publish the audio stream captured by the microphone
            options.publishMicrophoneTrack = true;
            // Publish the camera track
            options.publishCameraTrack = true;
            // Automatically subscribe to all audio streams
            options.autoSubscribeAudio = true;
            // Automatically subscribe to all video streams
            options.autoSubscribeVideo = true;
            // Join the channel using the temporary token obtained from the console
            m_rtcEngine->joinChannel(token, channelName, 0, options);
        }

        void CAgoraQuickStartDlg::setupRemoteVideo() {
            // Render remote view
            VideoCanvas canvas;
            canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
            canvas.uid = remoteUid;
            canvas.view = m_staRemote.GetSafeHwnd();
            m_rtcEngine->setupRemoteVideo(canvas);
            m_remoteRender = true;
        }

        void CAgoraQuickStartDlg::setupLocalVideo() {
            // Render local view
            VideoCanvas canvas;
            canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
            canvas.uid = 0;
            canvas.view = m_staLocal.GetSafeHwnd();
            m_rtcEngine->setupLocalVideo(canvas);
            // Preview the local video
            m_rtcEngine->startPreview();
        }

        void CAgoraQuickStartDlg::OnBnClickedBtnJoin() {
            // Join channel
            // Get the channel name
            CString strChannelName;
            m_edtChannelName.GetWindowText(strChannelName);
            if (strChannelName.IsEmpty()) {
                AfxMessageBox(_T("Fill channel name first"));
                return;
            }

            joinChannel(token, CW2A(strChannelName));
            // Render local view
            setupLocalVideo();
        }

        void CAgoraQuickStartDlg::OnBnClickedBtnLeave() {
            // Stop local video preview
            m_rtcEngine->stopPreview();
            // Leave the channel
            m_rtcEngine->leaveChannel();
            // Clear local view
            VideoCanvas canvas;
            canvas.uid = 0;
            m_rtcEngine->setupLocalVideo(canvas);
            m_remoteRender = false;
        }

        LRESULT CAgoraQuickStartDlg::OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lParam) {
            // Join channel success callback
            uid_t localUid = wParam;
            return 0;
        }

        LRESULT CAgoraQuickStartDlg::OnEIDUserJoined(WPARAM wParam, LPARAM lParam) {
            // Remote user joined callback
            uid_t remoteUid = wParam;
            if (m_remoteRender) {
                return 0;
            }

            // Render remote view
            setupRemoteVideo(remoteUid);
            return 0;
        }

        LRESULT CAgoraQuickStartDlg::OnEIDUserOffline(WPARAM wParam, LPARAM lParam) {
            // Remote user left callback
            uid_t remoteUid = wParam;
            if (!m_remoteRender) {
                return 0;
            }
            // Clear remote view
            VideoCanvas canvas;
            canvas.uid = remoteUid;
            m_rtcEngine->setupRemoteVideo(canvas);
            m_remoteRender = false;
            return 0;
        }
        ```
      </Accordion>
    </Accordions>

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

    To connect the sample code to your existing user interface, ensure that your UI includes the controls used to [Display the local video](#display-the-local-video) and [Display remote video](#display-remote-video).

    Alternatively, follow these steps to create a bare-bones UI for your project.

    ### Steps to create a minimalistic UI [#steps-to-create-a-minimalistic-ui]

    1. Switch the project to resource view in the right menu bar, and then open the `.Dialog` file.

    2. From **View > Toolbox**, select Add **Picture Control**, and in **Properties > Miscellaneous**, set the ID of the control to `IDC_STATIC_REMOTE`.

    3. From **View > Toolbox** , select Add **Picture Control** , and in **Properties > Miscellaneous** , set the control's ID to `IDC_STATIC_LOCAL`.

    4. To set up an input box for entering the channel name, from **View > Toolbox** , select add **Static Text** control, and change the description text to `Channel name` in the properties. Add an **Edit Control** as an input box, and in **Properties > Miscellaneous**, set the control's ID to `IDC_EDIT_CHANNEL`.

    5. To add join and leave channel buttons, open **View > Toolbox** and add two **Button** controls. In **Properties > Miscellaneous** , set the IDs to `ID_BTN_JOIN` and `ID_BTN_LEAVE`, and set the description text to **Join** and **Leave** respectively. Your user interface looks similar to the following:

       ![](https://assets-docs.agora.io/images/video-sdk/windows-quickstart-ui.png)

    Follow these steps to create a bare-bones UI for your project.

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

    To test your app, follow these steps:

    1. In Visual Studio, select local Windows debugger to start compiling the application.

    2. Enter the name of the channel you want to join in the input box and click the **Join** button to join the channel.

       You see yourself in the local view.

    3. Use the [Web demo](https://webdemo.agora.io/basicVideoCall/index.html) to join the same channel and test the following use-cases:

       * If users on both devices join the channel as hosts, they can see and hear each other.
       * If one user joins as host and the other as audience, the host can see themselves in the local video window; the audience can see the host in the remote video window and hear the host.

    ## Reference [#reference-4]

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

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

    ### Next steps [#next-steps-4]

    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](/en/realtime-media/video/build/authenticate-users/authentication-workflow).

    ### Sample project [#sample-project-4]

    Agora provides open source sample projects on [GitHub](https://github.com/AgoraIO/API-Examples/tree/main/windows/APIExample/) for your reference.

    * Download or view the [sample project](https://github.com/AgoraIO/API-Examples/tree/main/windows/APIExample/APIExample/Basic/LiveBroadcasting) for a more detailed example.
    * For a Windows C# implementation, see [this sample project](https://github.com/AgoraIO-Extensions/Agora-C_Sharp-SDK/tree/master/APIExample/src/Basic/JoinChannelVideo).

    ### API Reference [#api-reference-4]

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

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

    * [`enableVideo`](https://api-ref.agora.io/en/video-sdk/cpp/4.x/API/class_irtcengine.html#api_irtcengine_enablevideo)

    * [`startPreview`](https://api-ref.agora.io/en/video-sdk/cpp/4.x/API/class_irtcengine.html#api_irtcengine_startpreview)

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

    * [`release`](https://api-ref.agora.io/en/video-sdk/cpp/4.x/API/class_irtcengine.html#api_irtcengine_release)

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

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

    * [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-4]

    * [SDK error codes](/en/realtime-media/video/reference/error-codes)
    * [Connection status management](/en/realtime-media/video/build/manage-connection-and-quality/connection-status-management)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="electron">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="electron" />

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

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

    To start a Video Calling session, implement the following steps in your app:

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

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

    * **Send and receive audio and video**: All users can publish streams to the channel and subscribe to audio and video streams published by other users in the channel.

    ![Video calling workflow](https://assets-docs.agora.io/images/video-sdk/video-call.svg)

    ## Prerequisites [#prerequisites-5]

    * A camera and a microphone.
    * A valid Agora account and project. See [Agora account management](/en/realtime-media/video/manage-agora-account) for details.
    * [Node.js](https://nodejs.org/en/download/) 14 or higher.

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

    1. Create a new directory for your Electron project in a local folder. Add the following files to the root of the project directory:

       * `package.json`: Manages project dependencies and scripts.
       * `index.html`: Defines the app's user interface.
       * `main.js`: The main process entry point.
       * `renderer.js`: The renderer process script, responsible for interacting with the Agora Electron SDK.

    2. Create a user interface for your app based on your use-case.

       A basic user interface consists of an element for local video and an element for remote video. Refer to [Create a user interface](#create-a-user-interface) to get a bare bones sample layout.

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

    <Tabs defaultValue="new">
      <TabsList>
        <TabsTrigger value="new">
          npm integration
        </TabsTrigger>

        <TabsTrigger value="manual">
          Manual Integration
        </TabsTrigger>
      </TabsList>

      <TabsContent value="new">
        Add the Video SDK to your project using `npm`:

        1. Configure `package.json`

           * macOS

             ```json
             {
               "name": "electron-demo-app",
               "version": "0.1.0",
               "author": "your name",
               "description": "My Electron app",
               "main": "main.js",
               "scripts": {
                 "start": "electron ."
               },
               "agora_electron": {
                 "platform": "darwin",
                 "prebuilt": true
               },
               "dependencies": {
                 "agora-electron-sdk": "latest"
               },
               "devDependencies": {
                 "electron": "latest"
               }
             }
             ```

           * Windows

             ```json
             {
               "name": "electron-demo-app",
               "version": "0.1.0",
               "author": "your name",
               "description": "My Electron app",
               "main": "main.js",
               "scripts": {
                 "start": "electron ."
               },
               "agora_electron": {
                 "platform": "win32",
                 "prebuilt": true,
                 "arch": "ia32"
               },
               "dependencies": { "agora-electron-sdk": "latest" },
               "devDependencies": {"electron": "latest" }
             }
             ```

             **Configuration parameters**

             * `agora-electron-sdk`: Version number of the Agora Electron SDK. Set to latest for the most recent version, or specify a version number. See [`agora-electron-sdk`](https://www.npmjs.com/package/agora-electron-sdk) on npm for available versions.
             * `electron`: Electron version number. Versions 5.0.0 and above are supported. For macOS devices with M1 chips, use version 11.0.0 or higher.
             * `platform` (Optional): Target platform. Set to `darwin` for macOS or `win32` for Windows.
             * `prebuilt` (Optional): Set to `true` by default to prevent compatibility issues between Electron, Node.js, and the SDK.
             * `arch` (Optional): Target architecture. Defaults to your system architecture.

           <CalloutContainer type="info">
             <CalloutDescription>
               Different versions of Electron have different environment requirements. If you see errors due to a mismatched environment when running the project, refer to the official [Electron documentation](https://releases.electronjs.org/releases/stable) to choose the appropriate Electron and Node.js versions.
             </CalloutDescription>
           </CalloutContainer>

        2. To install the dependencies, open a terminal in your project root directory and run the following commands::

           * macOS

             ```bash
             npm install
             ```

           * Windows

             ```bash
             npm install -D --arch=ia32 electron
             npm install
             ```

        <CalloutContainer type="info">
          <CalloutDescription>
            * On Windows, you must first install the 32-bit version of Electron by running `npm install -D --arch=ia32 electron`, and then run `npm install`. Otherwise, you may encounter the error: “Not a valid Win32 application.”

            * If a `node_modules` folder exists in the project root directory, delete it before running `npm install` to avoid potential errors.
          </CalloutDescription>
        </CalloutContainer>
      </TabsContent>

      <TabsContent value="manual">
        Compile the latest SDK from the [Electron SDK repository](https://github.com/AgoraIO-Extensions/Electron-SDK/tree/main/) and integrate it into your application.
      </TabsContent>
    </Tabs>

    ## Implement Video Calling [#implement-video-calling-5]

    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">
        ![Video Calling 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.

    ### Set up the main process [#set-up-the-main-process]

    The `main.js` file is the entry point for your Electron application. It defines the main process, which is responsible for creating and managing browser windows. This script initializes the app, loads the UI from `index.html`, and ensures proper behavior across platforms.

    ```js
    const { app, BrowserWindow } = require("electron");
    const path = require("path");

    // If using Electron 9.x or later, set allowRendererProcessReuse to false
    app.allowRendererProcessReuse = false;

    function createWindow() {
        // Create the browser window
        const mainWindow = new BrowserWindow({
            width: 800,
            height: 600,
            webPreferences: {
                // preload: path.join(__dirname, "renderer.js"),
                // Enable Node integration and disable context isolation
                nodeIntegration: true,
                contextIsolation: false,
            },
        });

        // Load the contents of the index.html file
        mainWindow.loadFile("./index.html");
        // Open the Developer Tools
        mainWindow.webContents.openDevTools();
    }

    // Manage the browser window for the Electron app
    app.whenReady().then(() => {
        createWindow();
        // On macOS, create a new window if none are open
        app.on("activate", function () {
            if (BrowserWindow.getAllWindows().length === 0) {
                createWindow();
            }
        });
    });

    // Quit the Electron app when all windows are closed (except on macOS)
    app.on("window-all-closed", function () {
        if (process.platform !== "darwin") app.quit();
    });
    ```

    ### Handle permissions [#handle-permissions-3]

    Perform this step only when the target platform is macOS. Starting with macOS v10.14, you must check and obtain permission before accessing the camera or microphone. Use Electron’s `askForMediaAccess` method to request these permissions from the user.

    Add the following code to the `main.js` file:

    ```js
    // Check and request device permissions
    async function checkAndApplyDeviceAccessPrivilege() {
        // Check and request camera permission
        const cameraPrivilege = systemPreferences.getMediaAccessStatus('camera');
        console.log(`Camera privilege before applying: ${cameraPrivilege}`);
        if (cameraPrivilege !== 'granted') {
            await systemPreferences.askForMediaAccess('camera');
            console.log('Requested camera access from user');
        }

        // Check and request microphone permission
        const micPrivilege = systemPreferences.getMediaAccessStatus('microphone');
        console.log(`Microphone privilege before applying: ${micPrivilege}`);
        if (micPrivilege !== 'granted') {
            await systemPreferences.askForMediaAccess('microphone');
            console.log('Requested microphone access from user');
        }
    }

    checkAndApplyDeviceAccessPrivilege();
    ```

    ### Import dependencies [#import-dependencies]

    Import the modules and functions required to build the app using Video SDK. Add the following code to `renderer.js`:

    ```js
    const {
        createAgoraRtcEngine,
        ChannelProfileType,
        ClientRoleType,
        VideoSourceType,
        VideoViewSetupMode
    } = require("agora-electron-sdk");
    ```

    ### Specify connection parameters [#specify-connection-parameters]

    Provide the App ID, temporary token, and the channel name you used when generating the token. The engine uses these values to initialize and join the channel.

    ```json
    // Enter your App ID
    const APPID = "<-- Insert App Id -->";
    // Enter your temporary token
    let token = "<-- Insert Token -->";
    // Enter the channel name used when generating the token
    const channel = "<-- Insert Channel Name -->";
    // Specify a unique user ID for this session
    let uid = 123;
    ```

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

    For real-time communication, create an `IRtcEngine` object by calling `createAgoraRtcEngine` and then call `initialize` with `RtcEngineContext` to specify the [App ID](/en/realtime-media/video/manage-agora-account#get-the-app-id). Call `registerEventHandler` to register a custom [event handler](#subscribe-to-video-sdk-events) for managing user interactions within the channel.

    ```js
    let rtcEngine;
    const os = require("os");
    const path = require("path");
    const sdkLogPath = path.resolve(os.homedir(), "./test.log");

    // Create RtcEngine instance
    rtcEngine = createAgoraRtcEngine();

    // Initialize RtcEngine instance
    rtcEngine.initialize({
        appId: APPID,
        logConfig: { filePath: sdkLogPath }
    });
    ```

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

    Call the `enableVideo` method to enable the video module, and then call `startPreview` to start the local video preview.

    ```js
    // Enable the video module
    rtcEngine.enableVideo();
    // Start the local video preview
    rtcEngine.startPreview();
    ```

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

    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](/en/realtime-media/video/build/authenticate-users/deploy-token-server) in your security infrastructure. For the purpose of this guide [Generate a temporary token](/en/realtime-media/video/manage-agora-account#generate-temporary-tokens).

    * **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 Video Calling, set the `channelProfile` to `ChannelProfileCommunication` and the `clientRoleType` to `ClientRoleBroadcaster`.

    ```js
    // Join the channel using a temporary token
    rtcEngine.joinChannel(token, channel, uid, {
        // Set the channel profile to communication
        channelProfile: ChannelProfileType.ChannelProfileCommunication,
        // Set the user role to broadcaster; keep the default value for audience role
        clientRoleType: ClientRoleType.ClientRoleBroadcaster,
        // Publish audio collected from the microphone
        publishMicrophoneTrack: true,
        // Publish video collected from the camera
        publishCameraTrack: true,
        // Automatically subscribe to all audio streams
        autoSubscribeAudio: true,
        // Automatically subscribe to all video streams
        autoSubscribeVideo: true,
    });
    ```

    ### Register event handlers [#register-event-handlers]

    Call the `registerEventHandler` method to register the following callback events:

    * `onJoinChannelSuccess`: Triggered when the local user successfully joins a channel. After joining, call `setupLocalVideo` to configure the local video window.
    * `onUserJoined`: Triggered when a remote user joins the current channel. After the remote user joins, call `setupRemoteVideo` to configure the remote video window.
    * `onUserOffline`: Triggered when a remote user leaves the current channel. After the remote user leaves, call `setupRemoteVideo` to close the remote video window.

    ```js
    const EventHandles = {
        // Listen for the local user joining the channel
        onJoinChannelSuccess: ({ channelId, localUid }, elapsed) => {
            console.log('Successfully joined channel: ' + channelId);
            // After the local user joins the channel, set up the local video view
            rtcEngine.setupLocalVideo({
                 sourceType: VideoSourceType.VideoSourceCameraPrimary,
                 uid: uid,
                 view: localVideoContainer,
                 setupMode: VideoViewSetupMode.VideoViewSetupAdd,
            });
        },

        // Listen for remote users joining the channel
        onUserJoined: ({ channelId, localUid }, remoteUid, elapsed) => {
            console.log('Remote user ' + remoteUid + ' joined');
            // After a remote user joins the channel, set up the remote video view
            rtcEngine.setupRemoteVideo(
                {
                    sourceType: VideoSourceType.VideoSourceRemote,
                    uid: remoteUid,
                    view: remoteVideoContainer,
                    setupMode: VideoViewSetupMode.VideoViewSetupAdd,
                },
                { channelId },
            );
        },

        // Listen for users leaving the channel
        onUserOffline: ( { channelId, localUid }, remoteUid, reason ) => {
            console.log('Remote user ' + remoteUid + ' left the channel');
            // After a remote user leaves the channel, remove the remote video view
            rtcEngine.setupRemoteVideo(
                {
                    sourceType: VideoSourceType.VideoSourceRemote,
                    uid: remoteUid,
                    view: remoteVideoContainer,
                    setupMode: VideoViewSetupMode.VideoViewSetupRemove,
                },
            );
        },
    };

    // Register the event handler
    rtcEngine.registerEventHandler(EventHandles);
    ```

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

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

    To display the local video:

    ```js
    // Set up the local video view
    rtcEngine.setupLocalVideo({
          sourceType: VideoSourceType.VideoSourceCameraPrimary,
          uid: uid,
          view: localVideoContainer,
          setupMode: VideoViewSetupMode.VideoViewSetupAdd,
    });
    ```

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

    To display the remote video, call `setupRemoteVideo` to configure the remote video feed.

    ```js
    // Set up the remote video view
    rtcEngine.setupRemoteVideo(
        {
            sourceType: VideoSourceType.VideoSourceRemote,
            uid: remoteUid,
            view: remoteVideoContainer,
            setupMode: VideoViewSetupMode.VideoViewSetupAdd,
        },
        { channelId },
    );

    // Remove the remote video view
    rtcEngine.setupRemoteVideo(
        {
            sourceType: VideoSourceType.VideoSourceRemote,
            uid: remoteUid,
            view: remoteVideoContainer,
            setupMode: VideoViewSetupMode.VideoViewSetupRemove,
        },
    );
    ```

    Call this method when the client receives the `onUserJoined` callback.

    ### Start and close the app [#start-and-close-the-app-3]

    When a user launches your app, start Video Calling. When a user closes the app, stop Video Calling and release resources.

    1. To start Video Calling, [initialize the engine](#initialize-the-engine), [display the local video](#display-the-local-video) and [join a channel](#join-a-channel).

    2. When Video Calling ends, leave the channel and clean up resources:

       ```javascript
       // Stop the preview
       rtcEngine.stopPreview();
       // Leave the channel
       rtcEngine.leaveChannel();
       ```

       When you no longer need to interact, unregister the event handler and call `release` to release engine resources.

       ```javascript
       // Unregister the event handler
       rtcEngine.unregisterEventHandler(EventHandles);
       // Release the engine
       rtcEngine.release();
       ```

       <CalloutContainer type="warning">
         <CalloutTitle>
           caution
         </CalloutTitle>

         <CalloutDescription>
           After destroying the engine, you can no longer use SDK methods and callbacks. To use the real-time interaction functions again, create a new engine instance. See [Initialize the engine](#initialize-the-engine) for details.
         </CalloutDescription>
       </CalloutContainer>

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

    A complete code sample demonstrating the basic process of real-time interaction is provided for your reference. Replace the entire contents of the `main.js` and `renderer.js` with the following code samples:

    ### `main.js` [#mainjs]

    <Accordions>
      <Accordion title="Complete sample code for real-time Video Calling">
        ```javascript
        const { app, BrowserWindow, systemPreferences } = require("electron");
        const path = require("path");

        // If using Electron 9.x or later, set allowRendererProcessReuse to false
        app.allowRendererProcessReuse = false;

        // Check and request device permissions (macOS only)
        async function checkAndApplyDeviceAccessPrivilege() {
            if (process.platform === "darwin") {
                // Check and request camera permission
                const cameraPrivilege = systemPreferences.getMediaAccessStatus('camera');
                console.log(`Camera privilege before applying: ${cameraPrivilege}`);
                if (cameraPrivilege !== 'granted') {
                    await systemPreferences.askForMediaAccess('camera');
                    console.log('Requested camera access from user');
                }

                // Check and request microphone permission
                const micPrivilege = systemPreferences.getMediaAccessStatus('microphone');
                console.log(`Microphone privilege before applying: ${micPrivilege}`);
                if (micPrivilege !== 'granted') {
                    await systemPreferences.askForMediaAccess('microphone');
                    console.log('Requested microphone access from user');
                }
            }
        }

        function createWindow() {
            // Create a browser window
            const mainWindow = new BrowserWindow({
                width: 800,
                height: 600,
                webPreferences: {
                    // preload: path.join(__dirname, "renderer.js"),
                    // Set nodeIntegration to true and contextIsolation to false
                    nodeIntegration: true,
                    contextIsolation: false,
                },
            });

            // Load the content of the index.html file
            mainWindow.loadFile("./index.html");
            // Open Developer Tools
            mainWindow.webContents.openDevTools();
        }

        // Manage the browser window for the Electron app
        app.whenReady().then(async () => {
            await checkAndApplyDeviceAccessPrivilege();
            createWindow();

            // Create a new window if none are open (macOS specific)
            app.on("activate", function () {
                if (BrowserWindow.getAllWindows().length === 0) {
                    createWindow();
                }
            });
        });

        // Quit the Electron app when all windows are closed (Windows specific)
        app.on("window-all-closed", function () {
            if (process.platform !== "darwin") app.quit();
        });
        ```

        ### `renderer.js` [#rendererjs]

        ```javascript
        const {
            createAgoraRtcEngine,
            ChannelProfileType,
            ClientRoleType,
            VideoSourceType,
            VideoViewSetupMode,
            } = require("agora-electron-sdk");

        let rtcEngine;
        let localVideoContainer;
        let remoteVideoContainer;
        // Enter your App ID
        const APPID = "<-- Insert App Id -->";
        // Enter your temporary token
        let token = "<-- Insert Token -->";
        // Enter the channel name used when generating the token
        const channel = "<-- Insert Channel Name -->";
        // User ID, must be unique within the channel
        let uid = 123;

        const EventHandles = {
            // Listen for the local user joining the channel
            onJoinChannelSuccess: ({ channelId, localUid }, elapsed) => {
                console.log('Successfully joined channel: ' + channelId);
                // After the local user joins the channel, set up the local video window
                rtcEngine.setupLocalVideo({
                     sourceType: VideoSourceType.VideoSourceCameraPrimary,
                     uid: uid,
                     view: localVideoContainer,
                     setupMode: VideoViewSetupMode.VideoViewSetupAdd,
                });
            },

            // Listen for remote users joining the channel
            onUserJoined: ({ channelId, localUid }, remoteUid, elapsed) => {
                console.log('Remote user ' + remoteUid + ' joined');
                // After the remote user joins the channel, set up the remote video window
                rtcEngine.setupRemoteVideo(
                    {
                        sourceType: VideoSourceType.VideoSourceRemote,
                        uid: remoteUid,
                        view: remoteVideoContainer,
                        setupMode: VideoViewSetupMode.VideoViewSetupAdd,
                    },
                    { channelId },
                );
            },

            // Listen for users leaving the channel
            onUserOffline: ( { channelId, localUid }, remoteUid, reason ) => {
                console.log('Remote user ' + remoteUid + ' left the channel');
                // After the remote user leaves the channel, remove the remote video window
                rtcEngine.setupRemoteVideo(
                    {
                        sourceType: VideoSourceType.VideoSourceRemote,
                        uid: remoteUid,
                        view: remoteVideoContainer,
                        setupMode: VideoViewSetupMode.VideoViewSetupRemove,
                    },
                );
            },
        };

        window.onload = () => {
            const os = require("os");
            const path = require("path");
            localVideoContainer = document.getElementById("join-channel-local-video");
            remoteVideoContainer = document.getElementById("join-channel-remote-video");
            const sdkLogPath = path.resolve(os.homedir(), "./test.log");

            // Create an instance of RtcEngine
            rtcEngine = createAgoraRtcEngine();

            // Initialize the RtcEngine instance
            rtcEngine.initialize({
                appId: APPID,
                logConfig: { filePath: sdkLogPath }
            });

            // Register the event callbacks
            rtcEngine.registerEventHandler(EventHandles);

            // Enable the video module
            rtcEngine.enableVideo();

            // Start local video preview
            rtcEngine.startPreview();

            // Join the channel using a temporary token
            rtcEngine.joinChannel(token, channel, uid, {
                // Set the channel profile to communication
                channelProfile: ChannelProfileType.ChannelProfileCommunication,
                // Set the user role to broadcaster; keep default for audience
                clientRoleType: ClientRoleType.ClientRoleBroadcaster,
                // Publish audio captured by the microphone
                publishMicrophoneTrack: true,
                // Publish video captured by the camera
                publishCameraTrack: true,
                // Automatically subscribe to all audio streams
                autoSubscribeAudio: true,
                // Automatically subscribe to all video streams
                autoSubscribeVideo: true,
            });
        };
        ```
      </Accordion>
    </Accordions>

    <CalloutContainer type="info">
      <CalloutTitle>
        Information
      </CalloutTitle>

      <CalloutDescription>
        * In `renderer.js`, replace the values for `APPID`, `token`, and `channel` with your App ID, the temporary token from Agora Console, and the channel name you used to generate the token.
        * If you're targeting macOS v10.14 or later, add the device permission code to `main.js`. For more details, see [Get device permissions](#set-up-the-main-process).
      </CalloutDescription>
    </CalloutContainer>

    To test the complete code, see [Test the sample code](#test-the-sample-code).

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

    To connect the sample code to your existing UI, ensure that your `index.html` file includes the HTML elements used to [Display the local video](#display-the-local-video) and [Display remote video](#display-remote-video).

    Alternatively, use the following sample to create a minimal interface. Add the following code to `index.html`:

    ### Sample code to create the user interface [#sample-code-to-create-the-user-interface-4]

    ```html
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <title>Electron Quickstart</title>
      </head>
      <body>
        <h1>Electron Quickstart</h1>
        <!-- Add the local video view to the interface -->
        <div
          id="join-channel-local-video"
          style="width: 300px; height: 300px; float: left"
        ></div>
        <!-- Add the remote video view to the interface -->
        <div
          id="join-channel-remote-video"
          style="width: 300px; height: 300px; float: left"
        ></div>
      </body>
      <script src="./renderer.js"></script>
    </html>
    ```

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

    Take the following steps to test the sample code:

    1. Update the `APPID`, `token`, and `channel` parameter values in your code.

    2. To run the app, execute the following command in the project root directory:

       ```bash
       npm start
       ```

       You see a window pop up showing the local video.

    3. Invite a friend to run the demo app on a second device. Alternatively, use the [Web demo](https://webdemo.agora.io/basicVideoCall/index.html) to join the same channel.

       * If users on both devices join the channel as hosts, they can see and hear each other.
       * If one user joins as host and the other as audience, the host can see themselves in the local video window; the audience can see the host in the remote video window and hear the host.

    ## Reference [#reference-5]

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

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

    ### Next steps [#next-steps-5]

    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](/en/realtime-media/video/build/authenticate-users/authentication-workflow).

    ### Sample project [#sample-project-5]

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

    ### API Reference [#api-reference-5]

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

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

    * [`enableVideo`](https://api-ref.agora.io/en/video-sdk/electron/4.x/API/class_irtcengine.html#api_irtcengine_enablevideo)

    * [`startPreview`](https://api-ref.agora.io/en/video-sdk/electron/4.x/API/class_irtcengine.html#api_irtcengine_startpreview)

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

    * [`release`](https://api-ref.agora.io/en/video-sdk/electron/4.x/API/class_irtcengine.html#api_irtcengine_release)

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

    * [How can I resolve common development issues on Electron?](/en/api-reference/faq/integration/electron_faq)

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

    * [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-5]

    * [SDK error codes](/en/realtime-media/video/reference/error-codes)
    * [Connection status management](/en/realtime-media/video/build/manage-connection-and-quality/connection-status-management)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="flutter">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="flutter" />

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

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

    To start a Video Calling session, implement the following steps in your app:

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

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

    * **Send and receive audio and video**: All users can publish streams to the channel and subscribe to audio and video streams published by other users in the channel.

    ![Video calling workflow](https://assets-docs.agora.io/images/video-sdk/video-call.svg)

    ## Prerequisites [#prerequisites-6]

    * A camera and a microphone.
    * A valid Agora account and project. See [Agora account management](/en/realtime-media/video/manage-agora-account) for details.
    * [Flutter](https://docs.flutter.dev/get-started/install) 2.10.5 or higher with Dart 2.14.0 or higher.
    * [Android Studio](https://developer.android.com/studio), IntelliJ, VS Code, or another IDE that supports Flutter. See [Set up an editor](https://docs.flutter.dev/get-started/editor).
    * Prepare your development and testing environment according to your [target platform](/en/realtime-media/video/reference/supported-platforms).

      Run `flutter doctor` to confirm that your development environment is set up correctly for Flutter development.

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

    <Tabs defaultValue="new">
      <TabsList>
        <TabsTrigger value="new">
          Create a new project
        </TabsTrigger>

        <TabsTrigger value="existing">
          Add to an existing project
        </TabsTrigger>
      </TabsList>

      <TabsContent value="new">
        From the Terminal, run the following commands to create a new project named `agora_project`, or follow the [steps for your IDE](https://docs.flutter.dev/get-started/test-drive?tab=vscode#choose-your-ide):

        ```bash
        flutter create agora_project
        cd agora_project
        ```
      </TabsContent>

      <TabsContent value="existing">
        To add Video Calling to your existing project:

        1. Open your Flutter project and navigate to the `lib` folder.
        2. Add a new file to the `lib` folder and name it `agora_logic.dart`.
      </TabsContent>
    </Tabs>

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

    Install the Agora Video SDK and other dependencies.

    1. Add the [latest version](https://pub.dev/packages/agora_rtc_engine) of Agora Video SDK to your Flutter project:

       ```bash
       flutter pub add agora_rtc_engine
       ```

    2. Add the permission processing package:

       ```bash
       flutter pub add permission_handler
       ```

       The `dependencies` in your `pubspec.yaml` file should look like the following:

       ```yaml
       dependencies:
         flutter:
           sdk: flutter
         agora_rtc_engine: ^6.5.0  # Agora Flutter SDK, please use the latest version
         permission_handler: ^11.3.1  # Package for managing runtime permissions
         cupertino_icons: ^1.0.8
       ```

    3. Install the dependencies.

       Execute the following command in the project path:

       ```bash
       flutter pub get
       ```

    ## Implement Video Calling [#implement-video-calling-6]

    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">
        ![Video Calling 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.

    ### Import packages [#import-packages]

    Import the following packages in your dart file.

    ```dart
    import 'dart:async';

    import 'package:agora_rtc_engine/agora_rtc_engine.dart';
    import 'package:flutter/material.dart';
    import 'package:permission_handler/permission_handler.dart';
    ```

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

    For real-time communication, initialize an `RtcEngine` instance. Use `RtcEngineContext` to specify the [App ID](/en/realtime-media/video/manage-agora-account#get-the-app-id), and other configuration parameters. In your dart file, add the following code:

    ```dart
    // Set up the Agora RTC engine instance
    Future<void> _initializeAgoraVoiceSDK() async {
      _engine = createAgoraRtcEngine();
      await _engine.initialize(const RtcEngineContext(
        appId: "<-- Insert app Id -->",
        channelProfile: ChannelProfileType.channelProfileCommunication,
      ));
    }
    ```

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

    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](/en/realtime-media/video/build/authenticate-users/deploy-token-server) in your security infrastructure. For the purpose of this guide [Generate a temporary token](/en/realtime-media/video/manage-agora-account#generate-temporary-tokens).

    * **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 Video Calling, set the `clientRoleType` to `clientRoleBroadcaster`.

    ```dart
    // Join a channel
    Future<void> _joinChannel() async {
      await _engine.joinChannel(
        token: token,
        channelId: channel,
        options: const ChannelMediaOptions(
          autoSubscribeVideo: true, // Automatically subscribe to all video streams
          autoSubscribeAudio: true, // Automatically subscribe to all audio streams
          publishCameraTrack: true, // Publish camera-captured video
          publishMicrophoneTrack: true, // Publish microphone-captured audio
          // Use clientRoleBroadcaster to act as a host or clientRoleAudience for audience
          clientRoleType: ClientRoleType.clientRoleBroadcaster,
        ),
        uid: 0,
      );
    }
    ```

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

    The SDK provides the `RtcEngineEventHandler` for subscribing to channel events. To use it, pass an instance of `RtcEngineEventHandler` to `registerEventHandler` and implement the event methods you want to handle.

    Call `registerEventHandler` to bind the event handler to the SDK.

    ```dart
    // Register an event handler for Agora RTC
    void _setupEventHandlers() {
      _engine.registerEventHandler(
        RtcEngineEventHandler(
          onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
            debugPrint("Local user ${connection.localUid} joined");
            setState(() => _localUserJoined = true);
          },
          onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
            debugPrint("Remote user $remoteUid joined");
            setState(() => _remoteUid = remoteUid);
          },
          onUserOffline: (RtcConnection connection, int remoteUid, UserOfflineReasonType reason) {
            debugPrint("Remote user $remoteUid left");
            setState(() => _remoteUid = null);
          },
        ),
      );
    }
    ```

    When a remote user joins the channel, the `onUserJoined` callback is triggered. Use the remote user's `uid` returned in the callback, to create an `AgoraVideoView` control for displaying the video stream from the remote user.

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

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

    To display the local video, enable the video module by calling `enableVideo`, then start the local video preview with `startPreview`.

    ```dart
    Future<void> _setupLocalVideo() async {
      // The video module and preview are disabled by default.
      await _engine.enableVideo();
      await _engine.startPreview();
    }
    ```

    To render the local video, add the following widget inside your UI’s widget tree, such as in the build method of your `StatefulWidget`:

    ```dart
    // Displays the local user's video view using the Agora engine.
    Widget _localVideo() {
      return AgoraVideoView(
        controller: VideoViewController(
          rtcEngine: _engine, // Uses the Agora engine instance
          canvas: const VideoCanvas(
            uid: 0, // Specifies the local user
            renderMode: RenderModeType.renderModeHidden, // Sets the video rendering mode
          ),
        ),
      );
    }
    ```

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

    To render a remote video, add the following widget inside your UI’s widget tree, such as in the build method of your `StatefulWidget`:

    ```dart
    // If a remote user has joined, render their video, else display a waiting message
    Widget _remoteVideo() {
      if (_remoteUid != null) {
        return AgoraVideoView(
          controller: VideoViewController.remote(
            rtcEngine: _engine, // Uses the Agora engine instance
            canvas: VideoCanvas(uid: _remoteUid), // Binds the remote user's video
            connection: const RtcConnection(channelId: channel), // Specifies the channel
          ),
        );
      } else {
        return const Text(
          'Waiting for remote user to join...',
          textAlign: TextAlign.center,
        );
      }
    }
    ```

    ### Handle permissions [#handle-permissions-4]

    Request microphone and camera permissions for Video Calling.

    ```dart
    Future<void> _requestPermissions() async {
      await [Permission.microphone, Permission.camera].request();
    }
    ```

    <CalloutContainer type="info">
      <CalloutTitle>
        Information
      </CalloutTitle>

      <CalloutDescription>
        If your target platform is iOS or macOS, add the microphone and camera permission declarations required for real-time interaction to [`Info.plist`](https://help.apple.com/xcode/mac/current/#/dev3f399a2a6).

        | **Device** | **Key**                                | **Value**       |
        | :--------- | :------------------------------------- | :-------------- |
        | Microphone | Privacy - Microphone Usage Description | for audio calls |
        | Camera     | Privacy - Camera Usage Description     | for video calls |
      </CalloutDescription>
    </CalloutContainer>

    ### Start and close the app [#start-and-close-the-app-4]

    To start Video Calling, request microphone and camera permissions, initialize the Agora SDK instance, set up event handlers, join a channel, and display the local video.

    ```dart
    await _requestPermissions();
    await _initializeAgoraVideoSDK();
    await _setupLocalVideo();
    _setupEventHandlers();
    await _joinChannel();
    ```

    To stop Video Calling, leave the channel and release the engine instance.

    ```dart
    // Leaves the channel and releases resources
    Future<void> _cleanupAgoraEngine() async {
      await _engine.leaveChannel();
      await _engine.release();
    }
    ```

    <CalloutContainer type="info">
      <CalloutTitle>
        Warning
      </CalloutTitle>

      <CalloutDescription>
        After you call `release`, you no longer have access to the methods and callbacks of the SDK. To use Video Calling features again, create a new engine instance.
      </CalloutDescription>
    </CalloutContainer>

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

    A complete code sample demonstrating the basic process of real-time interaction is provided for your reference. To use the sample code, copy the following code to replace the entire contents of the `.dart` file in your project.

    <Accordions>
      <Accordion title="Complete sample code for real-time Video Calling">
        ```dart
        import 'dart:async';
        import 'package:agora_rtc_engine/agora_rtc_engine.dart';
        import 'package:flutter/material.dart';
        import 'package:permission_handler/permission_handler.dart';

        void main() => runApp(const MyApp());

        // Fill in the app ID obtained from Agora Console
        const appId = "<-- Insert app Id -->";
        // Fill in the temporary token generated from Agora Console
        const token = "<-- Insert token -->";
        // Fill in the channel name you used to generate the token
        const channel = "<-- Insert channel name -->";


        // Main App Widget
        class MyApp extends StatelessWidget {
          const MyApp({Key? key}) : super(key: key);

          @override
          Widget build(BuildContext context) {
            return const MaterialApp(
              home: MainScreen(),
            );
          }
        }

        // Video Call Screen Widget
        class MainScreen extends StatefulWidget {
          const MainScreen({Key? key}) : super(key: key);

          @override
          _MainScreenScreenState createState() => _MainScreenScreenState();
        }

        class _MainScreenScreenState extends State<MainScreen> {
          int? _remoteUid; // Stores remote user ID
          bool _localUserJoined = false; // Indicates if local user has joined the channel
          late RtcEngine _engine; // Stores Agora RTC Engine instance

          @override
          void initState() {
            super.initState();
            _startVideoCalling();
          }

          // Initializes Agora SDK
          Future<void> _startVideoCalling() async {
            await _requestPermissions();
            await _initializeAgoraVideoSDK();
            await _setupLocalVideo();
            _setupEventHandlers();
            await _joinChannel();
          }

          // Requests microphone and camera permissions
          Future<void> _requestPermissions() async {
            await [Permission.microphone, Permission.camera].request();
          }

          // Set up the Agora RTC engine instance
          Future<void> _initializeAgoraVideoSDK() async {
            _engine = createAgoraRtcEngine();
            await _engine.initialize(const RtcEngineContext(
              appId: appId,
              channelProfile: ChannelProfileType.channelProfileCommunication,
            ));
          }

          // Enables and starts local video preview
          Future<void> _setupLocalVideo() async {
            await _engine.enableVideo();
            await _engine.startPreview();
          }

          // Register an event handler for Agora RTC
          void _setupEventHandlers() {
            _engine.registerEventHandler(
              RtcEngineEventHandler(
                onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
                  debugPrint("Local user ${connection.localUid} joined");
                  setState(() => _localUserJoined = true);
                },
                onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
                  debugPrint("Remote user $remoteUid joined");
                  setState(() => _remoteUid = remoteUid);
                },
                onUserOffline: (RtcConnection connection, int remoteUid, UserOfflineReasonType reason) {
                  debugPrint("Remote user $remoteUid left");
                  setState(() => _remoteUid = null);
                },
              ),
            );
          }

          // Join a channel
          Future<void> _joinChannel() async {
            await _engine.joinChannel(
              token: token,
              channelId: channel,
              options: const ChannelMediaOptions(
                autoSubscribeVideo: true,
                autoSubscribeAudio: true,
                publishCameraTrack: true,
                publishMicrophoneTrack: true,
                clientRoleType: ClientRoleType.clientRoleBroadcaster,
              ),
              uid: 0,
            );
          }

          @override
          void dispose() {
            _cleanupAgoraEngine();
            super.dispose();
          }

          // Leaves the channel and releases resources
          Future<void> _cleanupAgoraEngine() async {
            await _engine.leaveChannel();
            await _engine.release();
          }

          @override
          Widget build(BuildContext context) {
            return Scaffold(
              appBar: AppBar(title: const Text('Agora Video Calling')),
              body: Stack(
                children: [
                  Center(child: _remoteVideo()),
                  Align(
                    alignment: Alignment.topLeft,
                    child: SizedBox(
                      width: 100,
                      height: 150,
                      child: Center(
                        child: _localUserJoined
                            ? _localVideo()
                            : const CircularProgressIndicator(),
                      ),
                    ),
                  ),
                ],
              ),
            );
          }

            // Displays remote video view
          Widget _localVideo() {
            return AgoraVideoView(
              controller: VideoViewController(
                rtcEngine: _engine,
                canvas: const VideoCanvas(
                  uid: 0,
                  renderMode: RenderModeType.renderModeHidden,
                ),
              ),
            );
          }

          // Displays remote video view
          Widget _remoteVideo() {
            if (_remoteUid != null) {
              return AgoraVideoView(
                controller: VideoViewController.remote(
                  rtcEngine: _engine,
                  canvas: VideoCanvas(uid: _remoteUid),
                  connection: const RtcConnection(channelId: channel),
                ),
              );
            } else {
              return const Text(
                'Waiting for remote user to join...',
                textAlign: TextAlign.center,
              );
            }
          }
        }
        ```
      </Accordion>
    </Accordions>

    <CalloutContainer type="info">
      <CalloutTitle>
        Information
      </CalloutTitle>

      <CalloutDescription>
        In the `appId` and `token` fields, enter the corresponding values you obtained from Agora Console. Use the same `channel` name you filled in when generating the temporary token.
      </CalloutDescription>
    </CalloutContainer>

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

    To connect the sample code to your existing UI, ensure that your widget tree includes the `_remoteVideo` and  `_localVideo` widgets used to [Display the local video](#display-the-local-video) and [Display remote video](#display-remote-video).

    Alternatively, use the following sample code to generate a basic user interface:

    ### Sample code to create the user interface [#sample-code-to-create-the-user-interface-5]

    ```dart
    // Build UI to display local video and remote video
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(title: const Text('Agora Video Call')),
        body: Stack(
          children: [
            Center(child: _remoteVideo()),
            Align(
              alignment: Alignment.topLeft,
              child: SizedBox(
                width: 100,
                height: 150,
                child: Center(
                  child: _localUserJoined
                      ? _localVideo()
                      : const CircularProgressIndicator(),
                ),
              ),
            ),
          ],
        ),
      );
    }
    ```

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

    Take the following steps to test the sample code:

    1. In `main.dart` update the values for `appId`, and `token` with values from Agora Console. Fill in the same `channel` name you used to generate the token.

    2. Connect a target device to your development computer.

    3. Open Terminal and execute the following command in the project folder to run the sample project:

       ```bash
       flutter run
       ```

    4. Launch the App, grant microphone and camera permissions. If you set the user role to host, you see yourself in the local view.

    5. On a second target device, repeat the previous steps to install and launch the client. Alternatively, use the [Web demo](https://webdemo.agora.io/basicVideoCall/index.html) to join the same channel and test the following use-cases:

       * If users on both devices join the channel as hosts, they can see and hear each other.
       * If one user joins as host and the other as audience, the host can see themselves in the local video window; the audience can see the host in the remote video window and hear the host.

       On an Android device, the app UI appears similar to the following:

       ![quickstart-ui-flutter.png](https://assets-docs.agora.io/images/video-sdk/quickstart-ui-flutter.png)

    ## Reference [#reference-6]

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

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

    ### Next steps [#next-steps-6]

    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](/en/realtime-media/video/build/authenticate-users/authentication-workflow).

    ### Sample project [#sample-project-6]

    Agora provides open source sample projects on [GitHub](https://github.com/AgoraIO-Extensions/Agora-Flutter-SDK/tree/main/example/lib/examples) for your reference. Download or view [join\_channel\_video.dart](https://github.com/AgoraIO-Extensions/Agora-Flutter-SDK/tree/main/example/lib/examples/basic/join_channel_video) for a more detailed example.

    ### API reference [#api-reference-6]

    * [`enableVideo`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_enablevideo)

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

    * [`setClientRole`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_setclientrole2)

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

    * [`startPreview`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_startpreview2)

    * [`AgoraVideoView`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_agoravideoview.html)

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

    * [`leaveChannel`](https://api-ref.agora.io/en/video-sdk/flutter/6.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#black-screen-on-the-local-side)

    * [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-6]

    * [SDK error codes](/en/realtime-media/video/reference/error-codes)
    * [Connection status management](/en/realtime-media/video/build/manage-connection-and-quality/connection-status-management)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="react-native">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="react-native" />

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

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

    To start a Video Calling session, implement the following steps in your app:

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

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

    * **Send and receive audio and video**: All users can publish streams to the channel and subscribe to audio and video streams published by other users in the channel.

    ![Video calling workflow](https://assets-docs.agora.io/images/video-sdk/video-call.svg)

    ## Prerequisites [#prerequisites-7]

    * A camera and a microphone.
    * A valid Agora account and project. See [Agora account management](/en/realtime-media/video/manage-agora-account) for details.
    * React Native 0.60 or later. See [Get Started with React Native](https://reactnative.dev/docs/environment-setup).
    * Node.js 16 or later.
    * Depending on your target platform:
      * Android: a machine running macOS, Windows, or Linux; [JDK 11](https://openjdk.org/projects/jdk/11/) or later; the latest version of Android Studio; and a physical or virtual Android device running Android 5.0 or later.
      * iOS: a machine running macOS; Xcode 10 or later; CocoaPods; and a physical or virtual iOS 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.

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

    ### 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:

    <Tabs defaultValue="npm">
      <TabsList>
        <TabsTrigger value="npm">
          npm
        </TabsTrigger>

        <TabsTrigger value="yarn">
          Yarn
        </TabsTrigger>
      </TabsList>

      <TabsContent value="npm">
        In your project folder, execute the following command:

        ```bash
        npm i --save react-native-agora
        ```
      </TabsContent>

      <TabsContent value="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
        ```
      </TabsContent>
    </Tabs>

    <CalloutContainer type="info">
      <CalloutTitle>
        information
      </CalloutTitle>

      <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 Video Calling [#implement-video-calling-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">
        ![Video Calling 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,
        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](/en/realtime-media/video/manage-agora-account#get-the-app-id).

    ```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](/en/realtime-media/video/build/authenticate-users/deploy-token-server) in your security infrastructure. For the purpose of this guide [Generate a temporary token](/en/realtime-media/video/manage-agora-account#generate-temporary-tokens).

    * **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 Video Calling, set the `channelProfile` to `ChannelProfileCommunication` and the `clientRoleType` to `ClientRoleBroadcaster`.

    ```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.ChannelProfileCommunication,
                // 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.ChannelProfileCommunication,
                // 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,',
            });
        }
    };
    ```

    <CalloutContainer type="info">
      <CalloutDescription>
        Since the 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.

    <Tabs defaultValue="android">
      <TabsList>
        <TabsTrigger value="android">
          Android
        </TabsTrigger>

        <TabsTrigger value="ios">
          iOS
        </TabsTrigger>
      </TabsList>

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

        ```tsx
        // Import components related to Android device permissions

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

      <TabsContent 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.     |
      </TabsContent>
    </Tabs>

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

    To exit a Video Calling channel, call `leaveChannel`.

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

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

    Before closing your client, 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 Video Calling">
        ```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.ChannelProfileCommunication,
                            // 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.ChannelProfileCommunication,
                            // 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,
                        });
                    }
                } 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 [#sample-code-to-create-the-user-interface-6]

    ```jsx
    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">
         <CalloutTitle>
           Caution
         </CalloutTitle>

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

    <Tabs defaultValue="android">
      <TabsList>
        <TabsTrigger value="android">
          Android
        </TabsTrigger>

        <TabsTrigger value="ios">
          iOS
        </TabsTrigger>
      </TabsList>

      <TabsContent value="android">
        To run the client on an Android device:

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

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

      <TabsContent value="ios">
        To run the client on an iOS device:

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

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

        3. In Xcode, click the **Build and Run** button.
      </TabsContent>
    </Tabs>

    <CalloutContainer type="info">
      <CalloutTitle>
        Information
      </CalloutTitle>

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

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

    2. Invite a friend to install and run the client 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 of this product.

    * If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](/en/realtime-media/video/build/manage-connection-and-quality/cloud-proxy) 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](/en/realtime-media/video/build/authenticate-users/authentication-workflow).

    ### 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-7]

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

    * [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)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="javascript">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="javascript" />

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

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

    To start a Video Calling session, implement the following steps in your app:

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

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

    * **Send and receive audio and video**: All users can publish streams to the channel and subscribe to audio and video streams published by other users in the channel.

    ![Video calling workflow](https://assets-docs.agora.io/images/video-sdk/video-call.svg)

    <CalloutContainer type="info">
      <CalloutTitle>
        React SDK and Web SDK relationship
      </CalloutTitle>

      <CalloutDescription>
        The React SDK is built on Web SDK 4.x and includes its APIs. For example, `useLocalMicrophoneTrack` calls the Web SDK `createMicrophoneAudioTrack` method.

        Use the React SDK for basic real-time audio and video features. For advanced or complex scenarios, use the Web SDK. When a feature requires the Web SDK, the React SDK documentation links to the relevant Web SDK API pages.
      </CalloutDescription>
    </CalloutContainer>

    ## Prerequisites [#prerequisites-8]

    * A camera and a microphone.
    * A valid Agora account and project. See [Agora account management](/en/realtime-media/video/manage-agora-account) for details.
    * A [supported browser](/en/realtime-media/video/reference/supported-platforms#mobile-browsers).
    * A JavaScript package manager such as [npm](https://www.npmjs.com/package/npm).

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

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

    To create a new ReactJS project with the necessary dependencies:

    1. Ensure that you have installed [Node.js LTS](https://nodejs.org/en) and `npm`.

    2. Open a terminal and execute:

       ```bash
       npm create vite@latest agora-sdk-quickstart -- --template react-ts
       ```

       This creates a new project folder named `agora-sdk-quickstart`. Open the folder in your preferred IDE.

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

    Use one of the following methods to install the project dependencies:

    <Tabs defaultValue="npm">
      <TabsList>
        <TabsTrigger value="npm">
          npm
        </TabsTrigger>

        <TabsTrigger value="cdn">
          CDN
        </TabsTrigger>
      </TabsList>

      <TabsContent value="npm">
        Navigate to the project folder and run the following command to install the Video SDK:

        ```bash
        npm i agora-rtc-react
        ```
      </TabsContent>

      <TabsContent value="cdn">
        To integrate React JS dependencies and the Agora SDK using CDN, add the following code to your HTML file:

        ```html
        <!-- Include the React development libraries (must be introduced in this order) -->
        <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
        <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>

        <!-- If the above React libraries have already been integrated through npm, skip them and include only the Agora RTC React SDK -->
        <script src="https://download.agora.io/sdk/release/agora-rtc-react.2.3.0.js"></script>
        ```
      </TabsContent>
    </Tabs>

    ## Implement Video Calling [#implement-video-calling-8]

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

    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.

    ### Import Agora hooks and components [#import-agora-hooks-and-components]

    To use Video SDK in your component, include the following imports:

    ```jsx
    import {
      LocalUser, // Plays the microphone audio track and the camera video track
      RemoteUser, // Plays the remote user audio and video tracks
      useIsConnected, // Returns whether the SDK is connected to Agora's server
      useJoin, // Automatically join and leave a channel on mount and unmount
      useLocalMicrophoneTrack, // Create a local microphone audio track
      useLocalCameraTrack, // Create a local camera video track
      usePublish, // Publish the local tracks
      useRemoteUsers, // Retrieve the list of remote users
    } from "agora-rtc-react";
    import AgoraRTC, { AgoraRTCProvider } from "agora-rtc-react";
    import { useState } from "react";
    ```

    ### Initialize the client [#initialize-the-client]

    Use the `createClient` method provided by the SDK to create a client object for the `<AgoraRTCProvider />` component. Wrap the `<Basics />` component with `<AgoraRTCProvider />` to enable state management and access operation-related hooks.

    For Video Calling, set the `mode` property of `ClientConfig` to `"rtc"`.

    ```jsx
    export const VideoCalling = () => {
      const client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
      return(
          <AgoraRTCProvider client={client}>

          </AgoraRTCProvider>
      );
    }
    ```

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

    To join a channel, use the `useJoin` hook. You can specify the following `joinOptions`:

    * **App ID**: [`appid`](/en/realtime-media/video/manage-agora-account#get-the-app-id) identifies the project you created in Agora Console.

    * **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 `token` is 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](/en/realtime-media/video/build/authenticate-users/deploy-token-server) in your security infrastructure. For the purpose of this guide [Generate a temporary token](/en/realtime-media/video/manage-agora-account#generate-temporary-tokens).

    * **User ID**: `uid` is 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 do not set a user ID or set it to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value.

    Add the following to `Basics`:

    ```jsx
    const [appId, setAppId] = useState("<-- Insert App ID -->");
    const [channel, setChannel] = useState("<-- Insert Channel Name -->");
    const [token, setToken] = useState("<-- Insert Token -->");
    const [calling, setCalling] = useState(false);

    useJoin({appid: appId, channel: channel, token: token ? token : null}, calling);
    ```

    ### Create local audio and video tracks [#create-local-audio-and-video-tracks]

    To create local audio and video tracks, use the `useLocalMicrophoneTrack` and `useLocalCameraTrack` hooks. Add the following to `Basics`:

    ```jsx
    const { localMicrophoneTrack } = useLocalMicrophoneTrack(micOn);
    const { localCameraTrack } = useLocalCameraTrack(cameraOn);
    ```

    ### Publish tracks in the channel [#publish-tracks-in-the-channel]

    After joining a channel, publish the local audio and video tracks using the `usePublish` hook. Add the following to `Basics`:

    ```jsx
    const [micOn, setMic] = useState(true);
    const [cameraOn, setCamera] = useState(true);
    const { localMicrophoneTrack } = useLocalMicrophoneTrack(micOn);
    const { localCameraTrack } = useLocalCameraTrack(cameraOn);

    usePublish([localMicrophoneTrack, localCameraTrack]);
    ```

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

    To display the local video, use the `LocalUser` hook, which provides properties to manage local audio and video tracks. Assign the local video track to `videoTrack` and the audio track to `audioTrack`. Use the `micOn` and `cameraOn` parameters to toggle audio and video. Include the following code in the markup of your `Basics` component:

    ```jsx
    <LocalUser
      audioTrack={localMicrophoneTrack}
      cameraOn={cameraOn}
      micOn={micOn}
      videoTrack={localCameraTrack}
      style={{width: '50%', height: 300 }}
    >
    ```

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

    To manage and display the list of remote users connected to a channel, use the `useRemoteUsers` hook. To show each user's video, pass the user object to the `RemoteUser` component along with the required properties. Follow these steps to implement the logic:

    1. **Retrieve the list of remote users**

       Use the `useRemoteUsers` hook to get the current list of remote users:

       ```jsx
       const remoteUsers = useRemoteUsers();
       ```

    2. **Render the remoteUser component**

       Loop through the `remoteUsers` list and nest the `RemoteUser` component where you want to display each user's video. Include the following code in the markup of your `Basics` component:

       ```jsx
       {remoteUsers.map((user) => (
         <div key={user.uid}>
           <RemoteUser user={user} style={{ width: '50%', height: 300 }}>
             <samp>{user.uid}</samp>
           </RemoteUser>
         </div>
       ))}
       ```

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

    To leave a channel, destroy the component, close the browser window, or refresh the browser tab. The `useJoin` hook automatically handles the destruction of the React component.

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

    A complete code sample demonstrating the basic process of real-time interaction is provided for your reference. To use the complete sample, add the following to your `src/App.tsx` file.

    <Accordions>
      <Accordion title="Complete sample code for real-time Video Calling">
        ```jsx
        import {
          LocalUser,
          RemoteUser,
          useIsConnected,
          useJoin,
          useLocalMicrophoneTrack,
          useLocalCameraTrack,
          usePublish,
          useRemoteUsers,
        } from "agora-rtc-react";
        import { useState } from "react";
        import AgoraRTC, { AgoraRTCProvider } from "agora-rtc-react";


        export const VideoCalling = () => {
          const client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
          return(
                <AgoraRTCProvider client={client}>
                  <Basics />
                </AgoraRTCProvider>
          );
        }

        const Basics = () => {
          const [calling, setCalling] = useState(false);
          const isConnected = useIsConnected(); // Store the user's connection status
          const [appId, setAppId] = useState("<-- Insert App ID -->");
          const [channel, setChannel] = useState("<-- Insert Channel Name -->");
          const [token, setToken] = useState("<-- Insert Token -->");
          const [micOn, setMic] = useState(true);
          const [cameraOn, setCamera] = useState(true);
          const { localMicrophoneTrack } = useLocalMicrophoneTrack(micOn);
          const { localCameraTrack } = useLocalCameraTrack(cameraOn);

          useJoin({appid: appId, channel: channel, token: token ? token : null}, calling);
          usePublish([localMicrophoneTrack, localCameraTrack]);

          const remoteUsers = useRemoteUsers();

          return (
            <>
              <div>
                {isConnected ? (
                  <div>
                    <div>
                      <LocalUser
                        audioTrack={localMicrophoneTrack}
                        cameraOn={cameraOn}
                        micOn={micOn}
                        playAudio={false} // Plays the local user's audio track. You use this to test your microphone before joining a channel.
                        videoTrack={localCameraTrack}
                        style={{width: '90%', height: 300 }}
                      >
                        <samp>You</samp>
                      </LocalUser>
                    </div>
                    {remoteUsers.map((user) => (
                      <div key={user.uid}>
                        <RemoteUser user={user} style={{width: '90%', height: 300 }}>
                          <samp>{user.uid}</samp>
                        </RemoteUser>
                      </div>
                    ))}
                  </div>
                ) : (
                  <div>
                    <input
                      onChange={e => setAppId(e.target.value)}
                      placeholder="<Your app ID>"
                      value={appId}
                    />
                    <input
                      onChange={e => setChannel(e.target.value)}
                      placeholder="<Your channel Name>"
                      value={channel}
                    />
                    <input
                      onChange={e => setToken(e.target.value)}
                      placeholder="<Your token>"
                      value={token}
                    />

                    <button
                      disabled={!appId || !channel}
                      onClick={() => setCalling(true)}
                    >
                      <span>Join Channel</span>
                    </button>
                  </div>
                )}
              </div>
              {isConnected && (
                <div style={{padding: "20px"}}>
                  <div>
                    <button onClick={() => setMic(a => !a)}>
                      {micOn ? "Disable mic" : "Enable mic" }
                    </button>
                    <button onClick={() => setCamera(a => !a)}>
                      {cameraOn ? "Disable camera " : "Enable camera" }
                    </button>
                    <button
                      onClick={() => setCalling(a => !a)}
                      >
                      {calling ? "End calling" : "Start calling"}
                    </button>
                  </div>
                </div>
              )}
            </>
          );
        };

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

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

    Use [CodeSandbox](https://codesandbox.io/), to swiftly run the complete demo code and experience the Video Calling Video SDK functionality in just a few simple steps.

    <iframe src="https://codesandbox.io/embed/github/AgoraIO/quickstart-examples/tree/EN-docs/rtc/react-quickstart?codemirror=1&fontsize=14&hidenavigation=1&theme=dark&hidedevtools=1&module=/src/index.js" style="{ width: '100%', height: '500px', border: '0', borderRadius: '4px', overflow: 'hidden' }" title="agora-rtc-react-quickstart" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" />

    <p />

    To run the demo code in `CodeSandbox`:

    1. Fill in the app ID and the temporary token you obtained from Agora Console. Use the same channel name you used to generate the token.
    2. Click the **Join Channel** button. You see yourself in the video.
    3. Ask a friend to open the same demo link in their browser and join the channel using the same app ID, channel name, and temporary token as yours. After successfully joining the channel, you can see and hear each other.
    4. Click the microphone and camera buttons at the bottom to switch on, or turn off the corresponding devices.
    5. Click the hang-up button to end the call.

    To experiment and learn more, modify the code directly in the editor on the left, and preview the runtime effect on the right.

    <CalloutContainer type="info">
      <CalloutDescription>
        If `CodeSandbox` access is slow, try adjusting your network configuration.
      </CalloutDescription>
    </CalloutContainer>

    ## Reference [#reference-8]

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

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

    ### Next steps [#next-steps-8]

    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](/en/realtime-media/video/build/authenticate-users/authentication-workflow).

    ### Sample project [#sample-project-8]

    Agora provides an open source [sample project on GitHub](https://github.com/AgoraIO-Extensions/agora-rtc-react) for your reference. The project demonstrates the use of each component and hook provided by the React SDK, as well as some advanced functions.

    ### API reference [#api-reference-8]

    * [`LocalUser`](https://api-ref.agora.io/en/video-sdk/reactjs/2.x/functions/LocalUser.html)
    * [`RemoteUser`](https://api-ref.agora.io/en/video-sdk/reactjs/2.x/functions/RemoteUser.html)
    * [`useIsConnected()`](https://api-ref.agora.io/en/video-sdk/reactjs/2.x/functions/useIsConnected.html)
    * [`useJoin()`](https://api-ref.agora.io/en/video-sdk/reactjs/2.x/functions/useJoin.html)
    * [`useLocalMicrophoneTrack()`](https://api-ref.agora.io/en/video-sdk/reactjs/2.x/functions/useLocalMicrophoneTrack.html)
    * [`usePublish()`](https://api-ref.agora.io/en/video-sdk/reactjs/2.x/functions/usePublish.html)
    * [`useRemoteUsers()`](https://api-ref.agora.io/en/video-sdk/reactjs/2.x/functions/useRemoteUsers.html)

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

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

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="unity">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />

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

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

    To start a Video Calling session, implement the following steps in your app:

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

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

    * **Send and receive audio and video**: All users can publish streams to the channel and subscribe to audio and video streams published by other users in the channel.

    ![Video calling workflow](https://assets-docs.agora.io/images/video-sdk/video-call.svg)

    ## Prerequisites [#prerequisites-9]

    * A camera and a microphone.
    * A valid Agora account and project. See [Agora account management](/en/realtime-media/video/manage-agora-account) for details.
    * [Unity Hub](https://unity.com/download) and [Unity Editor](https://unity.com/releases/editor/archive) 2018.4.0 or higher.
    * A suitable operating system and compiler for your development platform:

      | Development platform | Operating system version | Compiler version                      |
      | :------------------- | :----------------------- | :------------------------------------ |
      | Android              | Android 4.1 or later     | Android Studio 4.1 or later           |
      | iOS                  | iOS 10.15 or later       | Xcode 9.0 or later                    |
      | macOS                | macOS 10.15 or later     | Xcode 9.0 or later                    |
      | Windows              | Windows 7 or later       | Microsoft Visual Studio 2017 or later |

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

    <Tabs defaultValue="new">
      <TabsList>
        <TabsTrigger value="new">
          Create a new project
        </TabsTrigger>

        <TabsTrigger value="existing">
          Add to an existing project
        </TabsTrigger>
      </TabsList>

      <TabsContent value="new">
        Refer to the following steps or the [Official Unity documentation](https://docs.unity3d.com/hub/manual/AddProject.html#add-projects) to create a Unity project.

        1. Open Unity and click **New**.

        2. Enter the following details:
           * **Project name** : The name of the project.
           * **Location** : Project storage path.
           * **Template** : The project type. Select **3D**.

        3. Click **Create project**.
      </TabsContent>

      <TabsContent value="existing">
        To open your existing project:

        1. In the **Projects** window, click the **Open** button in the top-right corner.

        2. Browse your file manager and select the folder of the project you want to open.

        3. Confirm your selection to add the project to the **Projects** window and open it in the Unity Editor.
      </TabsContent>
    </Tabs>

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

    1. Go to the [Download SDKs](/en/api-reference/sdks?platform=unity) page and download the latest version of the Unity SDK.
    2. In Unity Editor, navigate to **Assets** > **Import Package** > **Custom Package**, and select the unzipped SDK.

       All plugins are selected by default. Deselect any plugins you don't need, then click **Import**.

    ## Implement Video Calling [#implement-video-calling-9]

    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">
        ![Video Calling 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.

    Before proceeding, create and set up a script to implement Video Calling and bind the script to the canvas.

    ### Steps to set up a script [#steps-to-set-up-a-script]

    1. Create a new script and import the UI library.

       1. In the **Project** tab, navigate to **Assets > Agora-Unity-RTC-SDK > Code > Rtc**, right-click and select **Create > C# Script**. A new file named `NewBehaviourScript.cs` appears in your Assets.

       2. Rename the file to `JoinChannel.cs` and open it.

       3. Import the Unity namespaces to access UI components by adding the following code at the top of the file:

          ```c#
          using UnityEngine;
          using UnityEngine.UI;
          ```
    2. Bind the script to the canvas.

       In `Assets/Agora-Unity-RTC-SDK/Code/Rtc` , select the `JoinChannel.cs` file, and drag it to the Canvas. In the **Inspector** panel, ensure that the file is bound to the Canvas.

    ### Import Agora classes [#import-agora-classes-1]

    Import the `Agora.Rtc` namespace, which contains various classes and interfaces required to implement real-time audio and video functions.

    ```c#
    using Agora.Rtc;
    ```

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

    For real-time communication, create an `IRtcEngine` instance using `RtcEngine.CreateAgoraRtcEngine()`. Then, configure it using `Initialize(context)` with an `RtcEngineContext`, specifying the application context, App ID, and channel profile. In your `JoinChannel.cs` file, add the following code:

    ```c#
    internal IRtcEngine RtcEngine;
    // Fill in your app ID
    private string _appID= "";

    private void SetupVideoSDKEngine()
    {
        // Create an IRtcEngine instance
        RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
        RtcEngineContext context = new RtcEngineContext();
        context.appId = _appID;
        context.channelProfile = CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_COMMUNICATION;
        context.audioScenario = AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT;
        // Initialize the instance
        RtcEngine.Initialize(context);
    }
    ```

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

    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](/en/realtime-media/video/build/authenticate-users/deploy-token-server) in your security infrastructure. For the purpose of this guide [Generate a temporary token](/en/realtime-media/video/manage-agora-account#generate-temporary-tokens).

    * **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 Video Calling, set the `channelProfile` to `CHANNEL_PROFILE_COMMUNICATION` and the `clientRoleType` to `CLIENT_ROLE_BROADCASTER`.

    ```c#
    // Fill in your channel name
    private string _channelName = "";
    // Fill in a temporary token
    private string _token = "";

    public void Join()
    {
        // Set channel media options
        ChannelMediaOptions options = new ChannelMediaOptions();
        // Publish the audio stream collected from the microphone
        options.publishMicrophoneTrack.SetValue(true);
        // Publish the video stream collected from the camera
        options.publishCameraTrack.SetValue(true);
        // Automatically subscribe to all audio streams
        options.autoSubscribeAudio.SetValue(true);
        // Automatically subscribe to all video streams
        options.autoSubscribeVideo.SetValue(true);
        // Set the channel profile to live broadcasting
        options.channelProfile.SetValue(CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_COMMUNICATION);
        // Set the user role to broadcaster
        options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
        // Join the channel
        RtcEngine.JoinChannel(_token, _channelName, 0, options);
    }
    ```

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

    Create an instance of the `UserEventHandler` class and set it as the engine event handler. Override the callbacks based on your use-case.

    ```c#
    // Implement your own callback class by inheriting from the IRtcEngineEventHandler interface
    internal class UserEventHandler : IRtcEngineEventHandler
    {
        private readonly JoinChannelVideo _videoSample;
        internal UserEventHandler(JoinChannelVideo videoSample)
        {
            _videoSample = videoSample;
        }

        // Triggered when the local user successfully joins a channel
        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
        {
        }

        // Triggered when the SDK receives and successfully decodes the first frame of a remote video
        public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
        {
            // Set the display for the remote video
            _videoSample.RemoteView.SetForUser(uid, connection.channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
            // Start video rendering
            _videoSample.RemoteView.SetEnable(true);
            Debug.Log("Remote user joined");
        }

        // Triggered when the remote user leaves the channel
        public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
        {
            // Stop displaying the remote video
            _videoSample.RemoteView.SetEnable(false);
        }
    }
    ```

    Create an instance of the user callback class and call `InitEventHandler` to register the event handler.

    ```csharp
    private void InitEventHandler()
    {
        UserEventHandler handler = new UserEventHandler(this);
        RtcEngine.InitEventHandler(handler);
    }
    ```

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

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

    Use the following code to set up the local video view:

    ```csharp
    internal VideoSurface LocalView;

    private void PreviewSelf()
    {
        // Enable the video module
        RtcEngine.EnableVideo();
        // Enable local video preview
        RtcEngine.StartPreview();
        // Set up local video display
        LocalView.SetForUser(0, "");
        // Render the video
        LocalView.SetEnable(true);
    }
    ```

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

    When a remote user joins the channel, the `OnUserJoined` callback is triggered. Call  `SetForUser` to set the remote video display and call `SetEnable(true)` to render the video.

    ```c#
    internal VideoSurface RemoteView;
    // When the SDK receives the first frame of a remote video stream and successfully decodes it, the OnUserJoined callback is triggered.
    public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed) {
        // Set the remote video display
        _videoSample.RemoteView.SetForUser(uid, connection.channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
        // Start video rendering
        _videoSample.RemoteView.SetEnable(true);
        Debug.Log("Remote user joined");
    }
    ```

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

    Call `LeaveChannel` to leave the current channel.

    ```c#
    public void Leave() {
        Debug.Log("Leaving " + _channelName);
        // Leave the channel
        RtcEngine.LeaveChannel();
        // Disable the video module
        RtcEngine.DisableVideo();
        // Stop remote video rendering0
        RemoteView.SetEnable(false);
        // Stop local video rendering
        LocalView.SetEnable(false);
    }
    ```

    ### Handle permissions [#handle-permissions-6]

    To access the media devices, add device permissions to your project according to your target platform.

    <Tabs defaultValue="android">
      <TabsList>
        <TabsTrigger value="android">
          Android
        </TabsTrigger>

        <TabsTrigger value="apple">
          iOS/macOS
        </TabsTrigger>
      </TabsList>

      <TabsContent value="android">
        Since version 2018.3, Unity does not actively obtain device permissions from the user. Call `CheckPermission` to check for and obtain the necessary permissions.

        1. Include the `UnityEngine.Android` namespace, which contains Android-specific classes for interacting with Android devices from Unity:

           ```c#
           #if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
           using UnityEngine.Android;
           #endif
           ```

        2. Create a list of permissions to be obtained.

           ```c#
           #if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
           private ArrayList permissionList = new ArrayList() { Permission.Camera, Permission.Microphone };
           #endif
           ```

        3. Check if the required permissions have been granted. If not, prompt the user to grant the necessary permissions.

           ```c#
           private void CheckPermissions() {
               #if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
               foreach (string permission in permissionList) {
                   if (!Permission.HasUserAuthorizedPermission(permission)) {
                       Permission.RequestUserPermission(permission);
                   }
               }
               #endif
           }
           ```
      </TabsContent>

      <TabsContent value="apple">
        For iOS and macOS platforms, the Video SDK includes a post-build script named `BL_BuildPostProcess.cs`. When you build and export your Unity project as an iOS project, this script automatically inserts camera and microphone permission entries into the `Info.plist` file, eliminating the need for manual updates.
      </TabsContent>
    </Tabs>

    ### Start and stop your client [#start-and-stop-your-client]

    1. When the client starts, ensure that device permissions have been granted.

       ```c#
       void Update() {
           CheckPermissions();
       }
       ```

    2. To start Video Calling, initialize the engine and set up the event handler.

       ```c#
       void Start()
       {
           SetupVideoSDKEngine();
           InitEventHandler();
           PreviewSelf();
       }
       ```

    3. To clean up all session-related resources when a user exits the client, call the `Dispose` method of the `IRtcEngine`.

       ```c#
       void OnApplicationQuit() {
           if (RtcEngine != null) {
               Leave();
               // Destroy IRtcEngine
               RtcEngine.Dispose();
               RtcEngine = null;
           }
       }
       ```

       <CalloutContainer type="info">
         <CalloutDescription>
           After calling `Dispose`, you can no longer use any methods or callbacks of the SDK. To use Video Calling features again, create a new engine instance.
         </CalloutDescription>
       </CalloutContainer>

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

    A complete code sample demonstrating the basic process of real-time interaction is provided for your reference. To quickly implement the basic functions of real-time Video Calling, copy the following sample code into your project:

    ### Sample code to implement Video Calling in your client [#sample-code-to-implement-video-calling-in-your-client]

    <Accordions>
      <Accordion title="Complete sample code for real-time Video Calling">
        ```csharp
        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
        using UnityEngine.UI;
        using Agora.Rtc;

        #if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
        using UnityEngine.Android;
        #endif

        public class JoinChannelVideo : MonoBehaviour
        {
            // Fill in your app ID
            private string _appID= "";
            // Fill in your channel name
            private string _channelName = "";
            // Fill in your Token
            private string _token = "";
            internal VideoSurface LocalView;
            internal VideoSurface RemoteView;
            internal IRtcEngine RtcEngine;

            #if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
            private ArrayList permissionList = new ArrayList() { Permission.Camera, Permission.Microphone };
            #endif

            void Start()
            {
                SetupVideoSDKEngine();
                InitEventHandler();
                SetupUI();
                PreviewSelf();
            }

            void Update()
            {
                CheckPermissions();
            }

            void OnApplicationQuit()
            {
                if (RtcEngine != null)
                {
                    Leave();
                    // Destroy IRtcEngine
                    RtcEngine.Dispose();
                    RtcEngine = null;
                }
            }

            private void CheckPermissions() {
            #if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
                    foreach (string permission in permissionList)
                    {
                        if (!Permission.HasUserAuthorizedPermission(permission))
                        {
                            Permission.RequestUserPermission(permission);
                        }
                    }
            #endif
            }

            private void PreviewSelf()
            {
                // Enable video module
                RtcEngine.EnableVideo();
                // Start local video preview
                RtcEngine.StartPreview();
                // Set local video display
                LocalView.SetForUser(0, "");
                // Start rendering video
                LocalView.SetEnable(true);
            }

            private void SetupUI()
            {
                GameObject go = GameObject.Find("LocalView");
                LocalView = go.AddComponent<VideoSurface>();
                go.transform.Rotate(0.0f, 0.0f, -180.0f);
                go = GameObject.Find("RemoteView");
                RemoteView = go.AddComponent<VideoSurface>();
                go.transform.Rotate(0.0f, 0.0f, -180.0f);
                go = GameObject.Find("Leave");
                go.GetComponent<Button>().onClick.AddListener(Leave);
                go = GameObject.Find("Join");
                go.GetComponent<Button>().onClick.AddListener(Join);
            }

            private void SetupVideoSDKEngine()
            {
                // Create IRtcEngine instance
                RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
                RtcEngineContext context = new RtcEngineContext();
                    context.appId = _appID;
                    context.channelProfile = CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING;
                    context.audioScenario = AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT;
                // Initialize IRtcEngine
                RtcEngine.Initialize(context);
            }

            // Create an instance of the user callback class and set the callback
            private void InitEventHandler()
            {
                UserEventHandler handler = new UserEventHandler(this);
                RtcEngine.InitEventHandler(handler);
            }

            public void Join()
            {
                // Set channel media options
                ChannelMediaOptions options = new ChannelMediaOptions();
                // Start video rendering
                LocalView.SetEnable(true);
                // Publish microphone audio stream
                options.publishMicrophoneTrack.SetValue(true);
                // Publish camera video stream
                options.publishCameraTrack.SetValue(true);
                // Automatically subscribe to all audio streams
                options.autoSubscribeAudio.SetValue(true);
                // Automatically subscribe to all video streams
                options.autoSubscribeVideo.SetValue(true);
                // Set the channel profile to live broadcasting
                options.channelProfile.SetValue(CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING);
                // Set the user role to broadcaster
                options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
                // Join the channel
                RtcEngine.JoinChannel(_token, _channelName, 0, options);
            }

            public void Leave()
            {
                Debug.Log("Leaving _channelName");
                // Disable video module
                RtcEngine.StopPreview();
                // Leave the channel
                RtcEngine.LeaveChannel();
                // Stop remote video rendering
                RemoteView.SetEnable(false);
            }

            // Implement your own callback class by inheriting from the IRtcEngineEventHandler interface class
            internal class UserEventHandler : IRtcEngineEventHandler
            {
                private readonly JoinChannelVideo _videoSample;

                internal UserEventHandler(JoinChannelVideo videoSample)
                {
                    _videoSample = videoSample;
                }

                // Callback triggered when an error occurs
                public override void OnError(int err, string msg)
                {
                }

                // Callback triggered when the local user successfully joins the channel
                public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
                {
                }

                // OnUserJoined callback is triggered when the SDK receives and successfully decodes the first frame of remote video
                public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
                {
                    // Set remote video display
                    _videoSample.RemoteView.SetForUser(uid, connection.channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
                    // Start video rendering
                    _videoSample.RemoteView.SetEnable(true);
                    Debug.Log("Remote user joined");
                }

                // Callback triggered when a remote user leaves the current channel
                public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
                {
                    _videoSample.RemoteView.SetEnable(false);
                    Debug.Log("Remote user offline");
                }
            }
        }
        ```
      </Accordion>
    </Accordions>

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

    Follow these steps to set up a basic UI for your project or to integrate essential UI elements into your existing interface. A basic UI consists of the following components:

    * Local view window
    * Remote view window
    * Buttons to join and leave the channel

    ### Create a basic UI [#create-a-basic-ui]

    1. Create buttons to join and leave channel

       1. In your Unity project, right-click the **Sample Scene** and select **Game Object > UI > Button**. You see a button on the scene canvas.

       2. In the **Inspector** panel, rename the button to `Join` and adjust the position coordinates as needed. For example:

          * **Pos X**：`-329`
          * **Pos Y**: `-172`

       3. Select the **Text** control of the **Join** button , and change the text to `Join` in the **Inspector** panel.

       4. Repeat the steps to create a **Leave** button, using the following positions:

          * **Pos X**：`329`
          * **Pos Y**: `-172`

    2. Create local and remote view windows

       1. Right-click the Canvas and select **UI > Raw Image**.

       2. In the **Inspector** panel, rename `Raw Image` to `LocalView` and adjust its size and position on the canvas. For example:

          * **PosX**：`-250`
          * **Pos Y**: `0`
          * **Width**: `250`
          * **Height**: `250`

       3. Repeat the above steps to create a remote view window, name it `RemoteView` , and adjust its position on the canvas:

          * **PosX**：`250`
          * **Pos Y**: `0`
          * **Width**: `250`
          * **Height**: `250`

          Save the changes.

    At this point your UI looks similar to the following:

    ![](https://assets-docs.agora.io/images/video-sdk/video-call-ui-unity.png)

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

    Take the following steps to test the sample code:

    1. Obtain a temporary token from Agora Console.

    2. In `JoinChannel.cs`, update `_appID`, `_channelName`, and `_token` with the app ID, channel name, and temporary token for your project.

    3. In Unity Editor, click **Play** to run your project.

    4. Click **Join** to join a channel.

    5. Invite a friend to run the demo client on a second device. Use the same `_appID_`, `_token`, and `_channelName` to join. Alternatively, use the [Web demo](https://webdemo.agora.io/basicVideoCall/index.html) to join the same channel.

       After your friend joins successfully, you can hear and see each other.

    ## Reference [#reference-9]

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

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

    ### Next steps [#next-steps-9]

    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](/en/realtime-media/video/build/authenticate-users/authentication-workflow).

    ### Sample project [#sample-project-9]

    Agora provides open source sample projects on [GitHub](https://github.com/AgoraIO-Extensions/Agora-Unity-Quickstart/tree/main/API-Example-Unity/Assets/API-Example/Examples) for your reference. Download or view the [JoinChannelVideo](https://github.com/AgoraIO-Extensions/Agora-Unity-Quickstart/tree/main/API-Example-Unity/Assets/API-Example/Examples/Basic/JoinChannelVideo) project for a more detailed example.

    ### API reference [#api-reference-9]

    * [`JoinChannel`](https://api-ref.agora.io/en/video-sdk/unity/4.x/API/class_irtcengine.html#api_irtcengine_joinchannel)

    * [`EnableVideo`](https://api-ref.agora.io/en/video-sdk/unity/4.x/API/class_irtcengine.html#api_irtcengine_enablevideo)

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

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

    * [`SetClientRole`](https://api-ref.agora.io/en/video-sdk/unity/4.x/API/class_irtcengine.html#api_irtcengine_setclientrole)

    * [`LeaveChannel`](https://api-ref.agora.io/en/video-sdk/unity/4.x/API/class_irtcengine.html#api_irtcengine_leavechannel)

    * [`DisableVideo`](https://api-ref.agora.io/en/video-sdk/unity/4.x/API/class_irtcengine.html#api_irtcengine_disablevideo)

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

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

    * [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)

    ### See also [#see-also-8]

    * [SDK error codes](/en/realtime-media/video/reference/error-codes)
    * [Connection status management](/en/realtime-media/video/build/manage-connection-and-quality/connection-status-management)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="unreal">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unreal" />

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

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

    To start a Video Calling session, implement the following steps in your app:

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

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

    * **Send and receive audio and video**: All users can publish streams to the channel and subscribe to audio and video streams published by other users in the channel.

    ![Video calling workflow](https://assets-docs.agora.io/images/video-sdk/video-call.svg)

    ## Prerequisites [#prerequisites-10]

    * A camera and a microphone.

    * A valid Agora account and project. See [Agora account management](/en/realtime-media/video/manage-agora-account) for details.

    * Unreal Engine 4.27 or higher.

    * Prepare your development environment according to your target platform and engine version:

      | Dev environment requirements                                                                                   | Other requirements                                                                                                                                              |
      | :------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- |
      | [Android](https://docs.unrealengine.com/4.27/us-EN/SharingAndReleasing/Mobile/Android/AndroidSDKRequirements/) | -                                                                                                                                                               |
      | [iOS](https://docs.unrealengine.com/4.27/us-EN/SharingAndReleasing/Mobile/iOS/SDKRequirements/)                | A valid Apple developer signature.                                                                                                                              |
      | [macOS](https://docs.unrealengine.com/4.27/us-EN/Basics/InstallingUnrealEngine/RecommendedSpecifications/)     | A valid Apple developer signature.                                                                                                                              |
      | [Windows](https://docs.unrealengine.com/4.27/us-EN/Basics/InstallingUnrealEngine/RecommendedSpecifications/)   | 32-bit Windows supports Unreal Engine 4 only. To use Unreal Engine with 32-bit Windows, uncomment the code related to `Win32` in `AgoraPluginLibrary.Build.cs`. |

    * Two physical devices for testing.

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

    <Tabs>
      <TabsList>
        <TabsTrigger value="new">
          Create a new project
        </TabsTrigger>

        <TabsTrigger value="existing">
          Add to an existing project
        </TabsTrigger>
      </TabsList>

      <TabsContent value="new">
        Refer to the following steps or the [Unreal official guide](https://docs.unrealengine.com/4.27/us-EN/Basics/Projects/Browser/) to create a new project. If you already have an Unreal project, skip to the next section.

        1. Open Unreal Engine. Select **Games** under **New Project Categories**, and click **Next**.

        2. Configure your project as follows:

           * **Template**: Select **Blank**.
           * **Project Defaults**:
             * **Language**: Select &#x2A;*C++**.
             * **Target Platform**: Select **Desktop**.
           * **Project Location**: Enter the project files storage path.
           * **Project Name**: Type a suitable name for your project.

           Click **Create**.

           ![create-project-unreal](https://assets-docs.agora.io/images/video-sdk/create-project-unreal.jpg)
      </TabsContent>

      <TabsContent value="existing">
        1. In the Unreal Project Browser, click on **Browse** and locate the `.uproject` file.

        2. Select the project and click **Open**.
      </TabsContent>
    </Tabs>

    3. Add the Agora dependency library

       In `Project/Source/Project/Project.Build.cs`, add the `AgoraPlugin` using `PublicDependencyModuleNames.AddRange()`.

       ```cpp
       // Add the AgoraPlugin library
       PublicDependencyModuleNames.AddRange(new string[]
       {
           "Core",
           "CoreUObject",
           "Engine",
           "InputCore",
           "AgoraPlugin"
       });
       ```

    4. Create a new C++ class and generate header and library files

       In the Unreal Editor, select **Tools > New C++ Class**, then select **All Classes**, find **UserWidget** and name it `AgoraWidget`. Click **Create Class**. A new C++ class is added to your project and you see `AgoraWidget.h` and `AgoraWidget.cpp` files.

    5. Initialize custom Widget

       Add the following code to your widget header file:

       ```cpp
       protected:
       // Initialize custom Widget
       void NativeConstruct() override;
       ```

    6. Associate C++ classes and Widgets

       In Unreal Editor, click **Content Drawer**, select **Class Settings > Graph**, and set the **Parent Class** under **Class Options** to **AgoraWidget**.

       ![](https://assets-docs.agora.io/images/video-sdk/associate-classes-unreal.png)

    7. Create a user interface for your app. Refer to [Create a user interface](#create-a-user-interface) to create a bare bones UI. A basic user interface consists of the following elements:

       * Local user video window
       * Remote user video window
       * Join and leave channel buttons

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

    Take the following steps to add the Video Calling Video SDK to your project:

    1. Download the latest version of Agora Unreal Video SDK from [Download SDKs](/en/api-reference/sdks) and unzip it.
    2. In your project root folder, create a `Plugins` folder.
    3. Copy `AgoraPlugin` from the Unreal SDK folder to `Plugins`.

    ## Implement Video Calling [#implement-video-calling-10]

    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">
        ![Video Calling 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.

    ### Import Agora libraries [#import-agora-libraries]

    To import Agora library, add the following to `AgoraWidget.h`:

    ```cpp
    #include "AgoraPluginInterface.h"
    ```

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

    For real-time communication, initialize an `IRtcEngine` instance and set up an event handler to manage user interactions within the channel. Use `RtcEngineContext` to specify [App ID](/en/realtime-media/video/manage-agora-account#get-the-app-id), and custom [event handler](#subscribe-to-video-sdk-events), then call `RtcEngineProxy->initialize(RtcEngineContext)` to initialize the engine, enabling further channel operations.

    Add the `SetupSDKEngine` method declaration and implementation to the following files:

    * `AgoraWidget.h`

      ```cpp
      // Fill in your app ID
      FString _appID = "";
      // Define a global variable for IRtcEngine
      agora::rtc::IRtcEngine* RtcEngineProxy;

      private:
          // Create and initialize IRtcEngine
          void SetupSDKEngine();
      ```

    * `AgoraWidget.cpp`

      ```cpp
      void UAgoraWidget::SetupSDKEngine()
      {
          agora::rtc::RtcEngineContext RtcEngineContext;

          RtcEngineContext.appId = TCHAR_TO_ANSI(*_appID);
          RtcEngineContext.eventHandler = this;

          // Create IRtcEngine instance
          RtcEngineProxy = agora::rtc::ue::createAgoraRtcEngine();

          // Initialize IRtcEngine
          RtcEngineProxy->initialize(RtcEngineContext);
      }
      ```

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

    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](/en/realtime-media/video/build/authenticate-users/deploy-token-server) in your security infrastructure. For the purpose of this guide [Generate a temporary token](/en/realtime-media/video/manage-agora-account#generate-temporary-tokens).

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

    Add the `Join` method declaration and implementation to the following files:

    * `AgoraWidget.h`

      ```cpp
      // Fill in your channel name
      FString _channelName = "";
      // Fill in a valid token
      FString _token = "";

      UFUNCTION(BlueprintCallable)
      void Join();
      UFUNCTION(BlueprintCallable)
      void Leave();
      ```

    * `AgoraWidget.cpp`

      For Video Calling, set the `channelProfile` to `CHANNEL_PROFILE_COMMUNICATION` and the user role to `CLIENT_ROLE_BROADCASTER`.

      ```cpp
      void UAgoraWidget::Join()
      {
          // Enable the video module
          RtcEngineProxy->enableVideo();
          // Set channel media options
          agora::rtc::ChannelMediaOptions options;
          // Automatically subscribe to all audio streams
          options.autoSubscribeAudio = true;
          // Automatically subscribe to all video streams
          options.autoSubscribeVideo = true;
          // Publish video captured by the camera
          options.publishCameraTrack = true;
          // Publish audio captured by the microphone
          options.publishMicrophoneTrack = true;
          // Set the channel profile to live broadcasting
          options.channelProfile = agora::CHANNEL_PROFILE_TYPE::CHANNEL_PROFILE_COMMUNICATION;
          // Set the user role as host or audience
          options.clientRoleType = agora::rtc::CLIENT_ROLE_TYPE::CLIENT_ROLE_BROADCASTER;
          // Join a channel
          RtcEngineProxy->joinChannel(TCHAR_TO_ANSI(*_token), TCHAR_TO_ANSI(*_channelName), 0, options);
      }
      ```

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

    The Video SDK provides an interface for subscribing to channel events. To use it, inherit from the `IRtcEngineEventHandler` class and override the event handler methods for the events you want to process.

    * `AgoraWidget.h`

      ```cpp
      // Triggered when the local user leaves a channel
      void onLeaveChannel(const agora::rtc::RtcStats& stats) override;

      // Triggered when a remote user joins a channel
      void onUserJoined(agora::rtc::uid_t uid, int elapsed) override;

      // Triggered when a remote user leaves a channel
      void onUserOffline(agora::rtc::uid_t uid, agora::rtc::USER_OFFLINE_REASON_TYPE reason) override;

      // Triggered when the local user joins a channel
      void onJoinChannelSuccess(const char* channel, agora::rtc::uid_t uid, int elapsed) override;
      ```

    * `AgoraWidget.cpp`

      ```cpp
      // Implement the callback triggered after a remote user joins the channel
      void UAgoraWidget::onUserJoined(agora::rtc::uid_t uid, int elapsed)
      {
          // Set up remote video
          agora::rtc::VideoCanvas videoCanvas;
          videoCanvas.view = RemoteVideo;
          videoCanvas.uid = uid;
          videoCanvas.sourceType = agora::rtc::VIDEO_SOURCE_TYPE::VIDEO_SOURCE_REMOTE;

          agora::rtc::RtcConnection connection;
          connection.channelId = TCHAR_TO_ANSI(*_channelName);

          ((agora::rtc::IRtcEngineEx*)RtcEngineProxy)->setupRemoteVideoEx(videoCanvas, connection);
      }

      // Implement the callback triggered when a remote user leaves the channel
      void UAgoraWidget::onUserOffline(agora::rtc::uid_t uid, agora::rtc::USER_OFFLINE_REASON_TYPE reason)
      {
          // Stop remote video
          agora::rtc::VideoCanvas videoCanvas;
          videoCanvas.view = nullptr;
          videoCanvas.uid = uid;
          videoCanvas.sourceType = agora::rtc::VIDEO_SOURCE_TYPE::VIDEO_SOURCE_REMOTE;

          agora::rtc::RtcConnection connection;
          connection.channelId = TCHAR_TO_ANSI(*_channelName);

          ((agora::rtc::IRtcEngineEx*)RtcEngineProxy)->setupRemoteVideoEx(videoCanvas, connection);
      }

      // Implement the callback triggered when the local user joins the channel
      void UAgoraWidget::onJoinChannelSuccess(const char* channel, agora::rtc::uid_t uid, int elapsed)
      {
          AsyncTask(ENamedThreads::GameThread, [=]()
          {
              UE_LOG(LogTemp, Warning, TEXT("JoinChannelSuccess uid: %u"), uid);
              // Set up local video
              agora::rtc::VideoCanvas videoCanvas;
              videoCanvas.view = LocalVideo;
              videoCanvas.uid = 0;
              videoCanvas.sourceType = agora::rtc::VIDEO_SOURCE_TYPE::VIDEO_SOURCE_CAMERA;
              RtcEngineProxy->setupLocalVideo(videoCanvas);
          });
      }

      // Implement the callback triggered when the local user leaves the channel
      void UAgoraWidget::onLeaveChannel(const agora::rtc::RtcStats& stats)
      {
          AsyncTask(ENamedThreads::GameThread, [=]()
          {
              agora::rtc::VideoCanvas videoCanvas;
              videoCanvas.view = nullptr;
              videoCanvas.uid = 0;
              videoCanvas.sourceType = agora::rtc::VIDEO_SOURCE_TYPE::VIDEO_SOURCE_CAMERA;
              RtcEngineProxy->setupLocalVideo(videoCanvas);
          });
      }
      ```

    ### Handle permissions [#handle-permissions-7]

    To access the media devices, follow the steps for your target platform:

    <Tabs defaultValue="android">
      <TabsList>
        <TabsTrigger value="android">
          Android
        </TabsTrigger>

        <TabsTrigger value="apple">
          Apple
        </TabsTrigger>
      </TabsList>

      <TabsContent value="android">
        Add the `AndroidPermission` library to obtain device and network permissions:

        1. Add the following code to your header file:

           ```cpp
           #if PLATFORM_ANDROID
              #include "AndroidPermission/Classes/AndroidPermissionFunctionLibrary.h"
              #endif
           ```

        2. Add the `AndroidPermission` library to the `Project/Source/Project/Project.Build.cs` file:

           ```cpp
           if (Target.Platform == UnrealTargetPlatform.Android)
              {
                  PrivateDependencyModuleNames.AddRange(new string[] { "AndroidPermission" });
              }
           ```

        3. To check whether Android permissions have been granted, add the `CheckAndroidPermission` method and its implementation to the `AgoraWidget.h` and `AgoraWidget.cpp` files.

           * `AgoraWidget.h`

        ```cpp
        private:
                // Get Android permissions
                void CheckAndroidPermission();
        ```

        * `AgoraWidget.cpp`

        ```cpp
        void UAgoraWidget::CheckAndroidPermission()
              {
              #if PLATFORM_ANDROID
                  FString pathfromName = UGameplayStatics::GetPlatformName();
                  if (pathfromName == "Android")
                  {
                      TArray<FString> AndroidPermission;
                      AndroidPermission.Add(FString("android.permission.CAMERA"));
                      AndroidPermission.Add(FString("android.permission.RECORD_AUDIO"));
                      AndroidPermission.Add(FString("android.permission.READ_PHONE_STATE"));
                      AndroidPermission.Add(FString("android.permission.WRITE_EXTERNAL_STORAGE"));
                      AndroidPermission.Add(FString("android.permission.ACCESS_WIFI_STATE"));
                      AndroidPermission.Add(FString("android.permission.ACCESS_NETWORK_STATE"));
                      UAndroidPermissionFunctionLibrary::AcquirePermissions(AndroidPermission);
                  }
              #endif
              }

              void UAgoraWidget::NativeConstruct()
              {
                  Super::NativeConstruct();
              #if PLATFORM_ANDROID
                  CheckAndroidPermission()
              #endif
              }
        ```
      </TabsContent>

      <TabsContent value="apple">
        1. Refer to [How to add the permissions required for real-time interaction to an Unreal Engine project?](/en/realtime-media/video/)

        2. In `Project/Source/Project.Target.cs`, add the following code:

           ```cpp
           public class unrealstartTarget : TargetRules
              {
                  public unrealstartTarget( TargetInfo Target) : base(Target)
                  {
                      Type = TargetType.Game;
                      DefaultBuildSettings = BuildSettingsVersion.V2;
                      if (Target.Platform == UnrealTargetPlatform.IOS)
                      {
                          bOverrideBuildEnvironment = true;
                          GlobalDefinitions.Add("FORCE_ANSI_ALLOCATOR=1")
                      }
                      ExtraModuleNames.AddRange( new string[] { "unrealstart" } );
                  }
              }
           ```
      </TabsContent>
    </Tabs>

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

    Display the local video.

    ```cpp
    AsyncTask(ENamedThreads::GameThread, =
    {
        UE_LOG(LogTemp, Warning, TEXT("JoinChannelSuccess uid: %u"), uid);
        agora::rtc::VideoCanvas videoCanvas;
        videoCanvas.view = LocalVideo;
        videoCanvas.uid = 0;
        videoCanvas.sourceType = agora::rtc::VIDEO_SOURCE_TYPE::VIDEO_SOURCE_CAMERA;
        RtcEngineProxy->setupLocalVideo(videoCanvas);
    });
    ```

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

    When a remote user joins the channel, display their video.

    ```cpp
    // Set up remote video
    agora::rtc::VideoCanvas videoCanvas;
    videoCanvas.view = RemoteVideo;
    videoCanvas.uid = uid;
    videoCanvas.sourceType = agora::rtc::VIDEO_SOURCE_TYPE::VIDEO_SOURCE_REMOTE;

    agora::rtc::RtcConnection connection;
    connection.channelId = TCHAR_TO_ANSI(*_channelName);

    ((agora::rtc::IRtcEngineEx*)RtcEngineProxy)->setupRemoteVideoEx(videoCanvas, connection);
    ```

    ### Leave a channel [#leave-a-channel]

    To leave a channel call `leaveChannel`. Implement the following method:

    * `AgoraWidget.h`

      ```cpp
      UFUNCTION(BlueprintCallable)
      void Leave();
      ```

    * `AgoraWidget.cpp`

      ```cpp
      void UAgoraWidget::Leave()
      {
          // Leave the channel
          RtcEngineProxy->leaveChannel();
      }
      ```

    ### Release resources [#release-resources-1]

    When the local user leaves the channel, or exits the client, release memory by calling the `release` method of `IRtcEngine`. Override the `NativeDestruct()` method and add its implementation as follows:

    * `AgoraWidget.h`

      ```cpp
      void NativeDestruct() override;
      ```

    * `AgoraWidget.cpp`

      ```cpp
      void UAgoraWidget::NativeDestruct()
      {
          Super::NativeDestruct();
          if (RtcEngineProxy != nullptr)
          {
              RtcEngineProxy->unregisterEventHandler(this);
              RtcEngineProxy->release();
              delete RtcEngineProxy;

              RtcEngineProxy = nullptr;
          }
      }
      ```

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

    A complete code sample demonstrating the basic process of real-time interaction is provided for your reference. To use the sample code, copy each of the following code blocks and paste it in the corresponding file.

    ### `Project/Source/Project/AgoraWidget.h` [#projectsourceprojectagorawidgeth]

    <Accordions>
      <Accordion title="Complete sample code for real-time Video Calling">
        ```cpp
        #pragma once

        #include "CoreMinimal.h"
        #include "Blueprint/UserWidget.h"

        #if PLATFORM_ANDROID
        #include "AndroidPermission/Classes/AndroidPermissionFunctionLibrary.h"
        #endif

        #include "AgoraPluginInterface.h"
        #include "Components/Image.h"
        #include "Components/Button.h"
        #include "AgoraWidget.generated.h"

        UCLASS()
        class UNREALLEARNING_API UAgoraWidget : public UUserWidget, public agora::rtc::IRtcEngineEventHandler
        {
            GENERATED_BODY()

        public:
            // Fill in your app ID
            FString _appID = "";
            // Fill in your channel name
            FString _channelName = "";
            // Fill in a valid authentication token
            FString _token = "";

            UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
            UImage* RemoteVideo = nullptr;

            UPROPERTY(BlueprintReadWrite, meta = (BindWidget))
            UImage* LocalVideo = nullptr;

            UPROPERTY(VisibleAnywhere, BlueprintReadWrite, meta = (BindWidget))
            UButton* JoinBtn = nullptr;

            UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
            UButton* LeaveBtn = nullptr;

            UFUNCTION(BlueprintCallable)
            void Join();

            UFUNCTION(BlueprintCallable)
            void Leave();

            agora::rtc::IRtcEngine* RtcEngineProxy;

            // Callback triggered when the local user leaves the channel
            void onLeaveChannel(const agora::rtc::RtcStats& stats) override;
            // Callback triggered when a remote broadcaster successfully joins the channel
            void onUserJoined(agora::rtc::uid_t uid, int elapsed) override;
            // Callback triggered when a remote broadcaster leaves the channel
            void onUserOffline(agora::rtc::uid_t uid, agora::rtc::USER_OFFLINE_REASON_TYPE reason) override;
            // Callback triggered when the local user successfully joins the channel
            void onJoinChannelSuccess(const char* channel, agora::rtc::uid_t uid, int elapsed) override;

        private:
            void CheckAndroidPermission(); // Get Android permissions
            void SetupSDKEngine(); // Create and initialize IRtcEngine
            void SetupUI(); // Set up UI elements

        protected:
            void NativeConstruct() override; // Initialize the custom Widget
            void NativeDestruct() override; // Clean up all session-related resources
        };
        ```

        ### `Project/Source/Project/AgoraWidget.cpp` [#projectsourceprojectagorawidgetcpp]

        ```cpp
        #include "AgoraWidget.h"
        void UAgoraWidget::CheckAndroidPermission()
        {
        #if PLATFORM_ANDROID
            FString pathfromName = UGameplayStatics::GetPlatformName();
            if (pathfromName == "Android")
            {
                TArray AndroidPermission;
                AndroidPermission.Add(FString("android.permission.CAMERA"));
                AndroidPermission.Add(FString("android.permission.RECORD_AUDIO"));
                AndroidPermission.Add(FString("android.permission.READ_PHONE_STATE"));
                AndroidPermission.Add(FString("android.permission.WRITE_EXTERNAL_STORAGE"));
                UAndroidPermissionFunctionLibrary::AcquirePermissions(AndroidPermission);
            }
        #endif
        }
        void UAgoraWidget::SetupSDKEngine()
        {
            agora::rtc::RtcEngineContext RtcEngineContext;
            RtcEngineContext.appId = TCHAR_TO_ANSI(*_appID);
            RtcEngineContext.eventHandler = this;
            RtcEngineProxy = agora::rtc::ue::createAgoraRtcEngine();
            RtcEngineProxy->initialize(RtcEngineContext);
        }
        void UAgoraWidget::SetupUI()
        {
            JoinBtn->OnClicked.AddDynamic(this, &UAgoraWidget::Join);
            LeaveBtn->OnClicked.AddDynamic(this, &UAgoraWidget::Leave);
        }
        void UAgoraWidget::Join()
        {
            RtcEngineProxy->enableVideo();
            agora::rtc::ChannelMediaOptions options;
            options.autoSubscribeAudio = true;
            options.autoSubscribeVideo = true;
            options.publishCameraTrack = true;
            options.publishMicrophoneTrack = true;
            options.channelProfile = agora::CHANNEL_PROFILE_TYPE::CHANNEL_PROFILE_COMMUNICATION;
            options.clientRoleType = agora::rtc::CLIENT_ROLE_TYPE::CLIENT_ROLE_BROADCASTER;
            RtcEngineProxy->joinChannel(TCHAR_TO_ANSI(_token), TCHAR_TO_ANSI(_channelName), 0, options);
        }
        void UAgoraWidget::Leave()
        {
            RtcEngineProxy->leaveChannel();
        }
        void UAgoraWidget::NativeConstruct()
        {
            Super::NativeConstruct();
            #if PLATFORM_ANDROID
                CheckAndroidPermission()
            #endif
            SetupUI();
            SetupSDKEngine();
        }
        void UAgoraWidget::NativeDestruct()
        {
            Super::NativeDestruct();
            if (RtcEngineProxy != nullptr)
            {
                RtcEngineProxy->unregisterEventHandler(this);
                RtcEngineProxy->release();
                delete RtcEngineProxy;
                RtcEngineProxy = nullptr;
            }
        }
        void UAgoraWidget::onUserJoined(agora::rtc::uid_t uid, int elapsed)
        {
            agora::rtc::VideoCanvas videoCanvas;
            videoCanvas.view = RemoteVideo;
            videoCanvas.uid = uid;
            videoCanvas.sourceType = agora::rtc::VIDEO_SOURCE_TYPE::VIDEO_SOURCE_REMOTE;
            agora::rtc::RtcConnection connection;
            connection.channelId = TCHAR_TO_ANSI(*_channelName);
            ((agora::rtc::IRtcEngineEx*)RtcEngineProxy)->setupRemoteVideoEx(videoCanvas, connection);
        }
        void UAgoraWidget::onUserOffline(agora::rtc::uid_t uid, agora::rtc::USER_OFFLINE_REASON_TYPE reason)
        {
            agora::rtc::VideoCanvas videoCanvas;
            videoCanvas.view = nullptr;
            videoCanvas.uid = uid;
            videoCanvas.sourceType = agora::rtc::VIDEO_SOURCE_TYPE::VIDEO_SOURCE_REMOTE;
            agora::rtc::RtcConnection connection;
            connection.channelId = TCHAR_TO_ANSI(*_channelName);
            ((agora::rtc::IRtcEngineEx*)RtcEngineProxy)->setupRemoteVideoEx(videoCanvas, connection);
        }
        void UAgoraWidget::onJoinChannelSuccess(const char* channel, agora::rtc::uid_t uid, int elapsed)
        {
            AsyncTask(ENamedThreads::GameThread, =
            {
                UE_LOG(LogTemp, Warning, TEXT("JoinChannelSuccess uid: %u"), uid);
                agora::rtc::VideoCanvas videoCanvas;
                videoCanvas.view = LocalVideo;
                videoCanvas.uid = 0;
                videoCanvas.sourceType = agora::rtc::VIDEO_SOURCE_TYPE::VIDEO_SOURCE_CAMERA;
                RtcEngineProxy->setupLocalVideo(videoCanvas);
            });
        }
        void UAgoraWidget::onLeaveChannel(const agora::rtc::RtcStats& stats)
        {
            AsyncTask(ENamedThreads::GameThread, =
            {
                agora::rtc::VideoCanvas videoCanvas;
                videoCanvas.view = nullptr;
                videoCanvas.uid = 0;
                videoCanvas.sourceType = agora::rtc::VIDEO_SOURCE_TYPE::VIDEO_SOURCE_CAMERA;
                RtcEngineProxy->setupLocalVideo(videoCanvas);
            });
        }
        ```

        ### `Project/Source/Project/Project.Build.cs` [#projectsourceprojectprojectbuildcs]

        ```csharp
        using UnrealBuildTool;

        public class UnrealLearning : ModuleRules
        {
            public UnrealLearning(ReadOnlyTargetRules Target) : base(Target)
            {
                PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

                PublicDependencyModuleNames.AddRange(new string[]
                {
                    "Core",
                    "CoreUObject",
                    "Engine",
                    "InputCore",
                    "AgoraPlugin"
                });

                if (Target.Platform == UnrealTargetPlatform.Android)
                {
                    PrivateDependencyModuleNames.AddRange(new string[] { "AndroidPermission" });
                }

                PrivateDependencyModuleNames.AddRange(new string[] { });

                // Uncomment if you are using Slate UI
                // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });

                // Uncomment if you are using online features
                // PrivateDependencyModuleNames.Add("OnlineSubsystem");

                // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
            }
        }
        ```
      </Accordion>
    </Accordions>

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

    Follow these steps to set up a basic UI for your project or to integrate essential UI elements into your existing interface.

    ### Create a basic UI [#create-a-basic-ui-1]

    1. Create a **Widget Blueprint**.

       In Unreal Editor, click **Content Drawer > Content**, right-click and select **User Interface > Widget Blueprint**. Name the new Widget Blueprint **AgoraWidget** and double-click it to open.

       ![Create a Widget Blueprint](https://assets-docs.agora.io/images/video-sdk/unreal-setup-ui-1.png)

    2. Create a view canvas.

       Select **Palette > PANEL > Canvas Panel** and drag it to **AgoraWidget**.

       ![Add a Canvas Panel to the widget](https://assets-docs.agora.io/images/video-sdk/unreal-setup-ui-2.png)

    3. Create join and leave channel buttons in Widget Blueprint.

       1. In **AgoraWidget**, select **COMMON > Button**, drag it to the **Canvas Panel**, and rename it to **JoinBtn**. Adjust the button's size and position on the canvas or use the following sample settings:

          * **Position X**: `300`
          * **Position Y**: `700`
          * **Size X**: `240`
          * **Size Y**: `120`

       2. Select **COMMON > Text** and drag it to **JoinBtn**. Select the **Text** control of **JoinBtn**, and change the text content of **Text** to **Join** in the **Details** panel.

       3. Repeat the above steps to create a **LeaveBtn**. Adjust the button size and position according to your layout design.

    4. Create local and remote views in the Widget Blueprint.

       1. Select **COMMON > Image**, drag it to the **Canvas Panel**, and rename it **LocalVideo**. Adjust its position and size on the canvas. Use the following values, or specify as per your own layout design:

          * **Position X**: `350`
          * **Position Y**: `150`
          * **Size X**: `450`
          * **Size Y**: `450`

       2. Repeat the above steps to create a remote view and name it **RemoteVideo**. Adjust its position and size on the canvas. Use the following values, or specify as per your own layout design:

          * **Position X**: `1200`
          * **Position Y**: `150`
          * **Size X**: `450`
          * **Size Y**: `450`

    5. Save the changes. Your user interface in **Widget Blueprint** looks similar to the following:

       ![Widget Blueprint UI layout](https://assets-docs.agora.io/images/video-sdk/unreal-setup-ui-3.png)

    6. Create a **Level Blueprint** and associate it with the created **Widget Blueprint**.

       1. In **Unreal Editor**, click **Content Drawer**, right-click to select **Level**, and name it **agoraLevel**.

       2. Double-click to open **agoraLevel** and click **Open Level Blueprint**.

          ![Open the Level Blueprint](https://assets-docs.agora.io/images/video-sdk/unreal-setup-ui-4.png)

       3. Right-click and enter **Create Widget** in the search box. Select the created **AgoraWidget**, then create **Event BeginPlay** and **Add to Viewport** in the same way. Connect them as follows:

          ![Connect the Create Widget, Event BeginPlay, and Add to Viewport nodes](https://assets-docs.agora.io/images/video-sdk/unreal-setup-ui-5.png)

       4. Save your changes and run the project. The UI you have created looks similar to the following:

          ![The running app user interface](https://assets-docs.agora.io/images/video-sdk/unreal-setup-ui-6.png)

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

    Take the following steps to test the sample code:

    1. Obtain a temporary token from Agora Console.

    2. In `AgoraWidget.h`, update `_appID`, `_channelName`, and `_token` with the app ID, channel name, and temporary token for your project.

    3. In the Unreal Editor, click the play button to run your project, then click **Join** to join a channel.

    4. Invite a friend to run the demo client on a second device. Alternatively, use the [Web demo](https://webdemo.agora.io/basicVideoCall/index.html) to join the same channel.

       After your friend joins successfully, you can hear and see each other.

    ## Reference [#reference-10]

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

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

    ### Next steps [#next-steps-10]

    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](/en/realtime-media/video/build/authenticate-users/authentication-workflow).

    ### Sample project [#sample-project-10]

    Agora provides open source sample projects on [GitHub](https://github.com/AgoraIO-Extensions/Agora-Unreal-RTC-SDK/tree/main/Agora-Unreal-SDK-CPP-Example) for your reference. Download or view the [JoinChannelVideo](https://github.com/AgoraIO-Extensions/Agora-Unreal-RTC-SDK/tree/main/Agora-Unreal-SDK-CPP-Example/Source/AgoraExample/Examples/Basic/JoinChannelVideo) project for a more detailed example.

    To learn about Agora Unreal Blueprint development, refer to the [Unreal (Blueprint) Quickstart](/en/realtime-media/video/get-started-sdk?platform=blueprint).

    ### API reference [#api-reference-10]

    * [`JoinChannel`](https://api-ref.agora.io/en/video-sdk/unreal/4.x/API/class_irtcengine.html#api_irtcengine_joinchannel)

    * [`EnableVideo`](https://api-ref.agora.io/en/video-sdk/unreal/4.x/API/class_irtcengine.html#api_irtcengine_enablevideo)

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

    * [`SetClientRole`](https://api-ref.agora.io/en/video-sdk/unreal/4.x/API/class_irtcengine.html#api_irtcengine_setclientrole)

    * [`LeaveChannel`](https://api-ref.agora.io/en/video-sdk/unreal/4.x/API/class_irtcengine.html#api_irtcengine_leavechannel)

    * [`DisableVideo`](https://api-ref.agora.io/en/video-sdk/unreal/4.x/API/class_irtcengine.html#api_irtcengine_disablevideo)

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

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

    * [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-9]

    * [SDK error codes](/en/realtime-media/video/reference/error-codes)
    * [Connection status management](/en/realtime-media/video/build/manage-connection-and-quality/connection-status-management)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>

  <_PlatformPanel platform="blueprint">
    <_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="blueprint" />

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

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

    To start a Video Calling session, implement the following steps in your app:

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

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

    * **Send and receive audio and video**: All users can publish streams to the channel and subscribe to audio and video streams published by other users in the channel.

    ![Video calling workflow](https://assets-docs.agora.io/images/video-sdk/video-call.svg)

    ## Prerequisites [#prerequisites-11]

    * A camera and a microphone.

    * A valid Agora account and project. See [Agora account management](/en/realtime-media/video/manage-agora-account) for details.

    * Unreal Engine 4.27 or higher.

    * Prepare your development environment according to your target platform and engine version:

      | Dev environment requirements                                                                                   | Other requirements                                                                                                                                              |
      | :------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- |
      | [Android](https://docs.unrealengine.com/4.27/us-EN/SharingAndReleasing/Mobile/Android/AndroidSDKRequirements/) | -                                                                                                                                                               |
      | [iOS](https://docs.unrealengine.com/4.27/us-EN/SharingAndReleasing/Mobile/iOS/SDKRequirements/)                | A valid Apple developer signature.                                                                                                                              |
      | [macOS](https://docs.unrealengine.com/4.27/us-EN/Basics/InstallingUnrealEngine/RecommendedSpecifications/)     | A valid Apple developer signature.                                                                                                                              |
      | [Windows](https://docs.unrealengine.com/4.27/us-EN/Basics/InstallingUnrealEngine/RecommendedSpecifications/)   | 32-bit Windows supports Unreal Engine 4 only. To use Unreal Engine with 32-bit Windows, uncomment the code related to `Win32` in `AgoraPluginLibrary.Build.cs`. |

    * Two physical devices for testing.

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

    <Tabs defaultValue="new">
      <TabsList>
        <TabsTrigger value="new">
          Create a new project
        </TabsTrigger>

        <TabsTrigger value="existing">
          Add to an existing project
        </TabsTrigger>
      </TabsList>

      <TabsContent value="new">
        Refer to the following steps or the [Unreal official guide](https://docs.unrealengine.com/4.27/us-EN/Basics/Projects/Browser/) to create a new project. If you already have an Unreal project, skip to the next section.

        1. Open Unreal Engine. Select **Games** under **New Project Categories**, and click **Next**.

        2. Configure your project as follows:

           * **Template**: Select **Blank**.
           * **Project Defaults**:
             * **Language**: Select **Blueprint**.
             * **Target Platform**: Pick **Desktop**.
           * **Project Location**: Enter a project files storage path.
           * **Project Name**: Type a suitable name for your project.

           Click **Create**.

           ![create-project-unreal](https://assets-docs.agora.io/images/video-sdk/create-project-unreal.jpg)
      </TabsContent>

      <TabsContent value="existing">
        1. In the Unreal Project Browser, click on **Browse** and locate the `.uproject` file.

        2. Select the project and click **Open**.
      </TabsContent>
    </Tabs>

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

    Take the following steps to add the Video Calling Video SDK to your project:

    1. Download the latest version of Agora Unreal Video SDK from [Download SDKs](/en/api-reference/sdks) and unzip it.
    2. In your project root folder, create a `Plugins` folder.
    3. Copy `AgoraPlugin` from the Unreal SDK folder to `Plugins`.

    ## Implement Video Calling [#implement-video-calling-11]

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

    ### Create a level [#create-a-level]

    1. In the **Content** folder of the **Content Browser**, right-click and select **Level** to create a **Level Blueprint** and name it **BasicVideoCallScene**.

    2. Double-click **BasicVideoCallScene** and click **Blueprints > Open Level Blueprint** above the editor to open the level blueprint.

       ![](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-1.png)

    ### Implement basic processes [#implement-basic-processes]

    In the **My Blueprint** panel, double-click **Graphs > EventGraph** to open the event graph. You see two event nodes: **Event BeginPlay** (game starts) and **Event End Play** (game ends). Create event nodes with the corresponding functions and variables, and connect them as shown in the following figure to implement the Video Calling logic:

    ![](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-2.png)

    The following table lists the main nodes:

    | #  | Node                            | Type       | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
    | :- | :------------------------------ | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | 1  | **Set Show Mouse Cursor**       | Native\*   | (Optional) Set whether to display the mouse cursor. Check to display it.    <CalloutContainer type="info">
          <CalloutTitle>
            Information
          </CalloutTitle>

          <CalloutDescription>
            * This node is available on Windows and macOS only.
            * If the node is not retrieved at creation time, uncheck **Context Sensitive**. ![](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-3.png)
          </CalloutDescription>
        </CalloutContainer> |
    | 2  | **Load Agora Config**           | Custom\*\* | Loads Agora configuration. Used to verify user identity when creating and joining channels.                                                                                                                                                                                                                                                                                                                                                                                                                |
    | 3  | **Create BP Video Widget**      | Native     | Create user interface:1) Add **Create Widget** node.
    2) Select the node's **Class** as **BP\_VideoWidget** and associate the node with the already created user interface.                                                                                                                                                                                                                                                                                                                                 |
    | 4  | **Set Basic Video Call Widget** | Custom     | Set up the user interface:1) Create the **BasicVideoCallWidget** variable and select the **Variable Type** of the variable as **BP\_VideoWidget**, which is the user interface created to store a reference to the user interface in the blueprint.
    2) After dragging the created variables to **EventGraph**, two options, **Set BasicVideoCallWidget** and **Get BasicVideoCallWidget**, appear. Select **Set BasicVideoCallWidget** to create a node for accessing and setting the user interface.      |
    | 5  | **BindUIEvent**                 | Custom     | Use Bind UI events to handle event logic after clicking the **Join Channel** and **Leave Channel** buttons.                                                                                                                                                                                                                                                                                                                                                                                                |
    | 6  | **Add to Viewport**             | Native     | Add user interface to the viewport.                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
    | 7  | **Check Permission**            | Custom     | (Optional) Check whether you have obtained the system permissions required for real-time audio and video interaction, such as access to the camera and microphone.    <CalloutContainer type="warning">
          <CalloutTitle>
            Note
          </CalloutTitle>

          <CalloutDescription>
            If your target platform is Android, create this node to check system permissions.
          </CalloutDescription>
        </CalloutContainer>                                                                  |
    | 8  | **Init Rtc Engine**             | Custom     | Create and initialize the RTC engine.                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
    | 9  | **Un Init Rtc Engine**          | Custom     | Leave the channel and release resources.                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |

    \* Native nodes are nodes that come with the blueprint and can be added and called directly.<br />
    \*\* Custom nodes are not included in the blueprint. You create a custom function before you can add the corresponding node.

    ### Add channel-related variables [#add-channel-related-variables]

    Add variables to create an engine instance and join a channel.

    1. Create three variables:

       **Token**, **ChannelId**, and **AppId**. Select the **Variable Type** as **String**.

    2. In the **Load Agora Config** function, add the **Sequence** node, and then connect **Set Token**, **Set Channel Id**, and **Set App Id** respectively. Fill in the token, channel name, and app ID values obtained from Agora Console.

       ![](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-4.png)

    ### Initialize RTC engine [#initialize-rtc-engine]

    1. If your target platform is Android, check whether system permissions have been granted before initializing the RTC engine. Refer to the following figure to create nodes for adding permissions to access the microphone and camera in the **CheckPermission** function.

       ![](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-5.png)

       <CalloutContainer type="info">
         <CalloutTitle>
           Information
         </CalloutTitle>

         <CalloutDescription>
           If your target platform is macOS or iOS, please refer to [How do I add the permissions needed for real-time interaction to my Unreal Engine project?](/en/api-reference/faq/integration/unreal_permissions)
         </CalloutDescription>
       </CalloutContainer>

    2. To initialize the RTC engine, in the **InitRtcEngine** function, create and connect nodes as shown in the following figure:

       ![](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-6.png)

    3. Create `IRtcEngine` and `IRtcEngineEventHandler`.

       1. To store references to the engine and event-handler interface classes, create `RtcEngine` and `EventHandler` variables, and set the **Variable Type** to **Agora Rtc Engine** and **IRtc Engine Event Handler**, respectively.

       2. Add two **Construct Object From Class nodes**, set **Class** to **Agora Rtc Engine** and **IRtc Engine Event Handler** respectively. Connect to **Set Rtc Engine** and **Set Event Handler**, respectively.

    4. Bind `IRtcEngineEventHandler` class-related callback functions.

       1. Create `onJoinChannelSuccess`, `onLeaveChannel`, `onUserJoined`, and `onUserOffline` callback functions. Refer to the following table to configure the input parameters of the callbacks:

          | Callback                | Description                                   | Input parameters                                                                                                                                                                                                                        |
          | :---------------------- | :-------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
          | `FOnJoinChannelSuccess` | The local user successfully joined a channel. | * `channel`: (String) Channel name.
          * `uid`: (Integer64) The ID of the user joining the channel.
          * `elapsed`: (Integer) The time (in milliseconds) that elapsed from the local call to `JoinChannel` till the occurrence of this event. |
          | `FOnLeaveChannel`       | The local user left the channel.              | - `stats`: Call statistics.                                                                                                                                                                                                             |
          | `FOnUserJoined`         | A remote user joined the current channel.     | - `uid`: (Integer64) The user ID of the remote user joining the channel.
          - `elapsed`: (Integer) The time (in milliseconds) that elapsed from the local call to `JoinChannel` till the occurrence of this event.                         |
          | `FOnUserOffline`        | A remote user left the current channel.       | * `uid`: (Integer64) The ID of the user going offline.
          * `reason`: Offline reason. For details, see  `EUSER_OFFLINE_REASON_TYPE`.                                                                                                       |

       2. Create a **Bind Event** function. In this function, add a **Sequence** node, and then bind the `onJoinChannelSuccess`, `onLeaveChannel`, `onUserJoined`, and `onUserOffline` callback events.

          ![](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-7.png)

    5. `IRtcEngine` initialization

       1. Call **Initialize** to initialize the RTC engine.

       2. Connect to the **RtcEngineContext** configuration `IRtcEngine` instance and select **Channel Profile** as `CHANNEL_PROFILE_COMMUNICATION`.

    ### Bind UI events [#bind-ui-events]

    To bind UI events:

    1. Create and implement the `OnJoinChannelClicked` event callback.

       ![Join Channel Clicked](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-8.png)

       1. Call **Enable Video** and **Enable Audio** to enable the video and audio modules.
       2. Call **Join Channel** to join the channel.
       3. Set the following parameters in **Make ChannelMediaOptions**:
          * Set **Publish Camera Track** to `AGORA TRUE VALUE` to publish the video stream recorded by the camera.
          * Set **Publish Microphone Track** to `AGORA TRUE VALUE` to publish the audio stream recorded by the microphone.
          * Set **Auto Subscribe Video** to `AGORA TRUE VALUE` to automatically subscribe to all video streams.
          * Set **Auto Subscribe Audio** to `AGORA TRUE VALUE` to automatically subscribe to all audio streams.
          * Check **Client Role Type Set Value** and set **Client Role Type** to `CLIENT_ROLE_BROADCASTER` or `CLIENT_ROLE_AUDIENCE`, to set the user role to host or audience.
          * Check **Channel Profile Set Value** and set **Channel Profile** to `CHANNEL_PROFILE_LIVE_BROADCASTING`.

    2. Create and implement the `OnLeaveChannelClicked` event callback. When the event is triggered, call **Leave Channel** to leave the channel.

       ![Leave Channel Clicked](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-9.png)

    3. In the **Bind UIEvent** function, refer to the figure below to bind the `OnJoinChannelClicked` and `OnLeaveChannelClicked` callback functions to the **Join Channel** and **Leave Channel** buttons respectively. When the button is clicked, the corresponding event callback is triggered.

       ![Bind Join Leave Callbacks](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-10.png)

    <CalloutContainer type="info">
      <CalloutTitle>
        Information
      </CalloutTitle>

      <CalloutDescription>
        You can also bind UI events in Unreal Motion Graphics (UMG). This document only shows binding using the **Bind UIEvent Function**.
      </CalloutDescription>
    </CalloutContainer>

    ### Set up local and remote views [#set-up-local-and-remote-views]

    1. Create and implement the **MakeVideoView** function to load the view when local or remote users join the channel:

       ![Implement Make Video View](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-11.png)

       1. In this function, create `SavedUID`, `SavedSourceType`, `SavedChannelID` local variables, set the **Variable Type** to `Integer64`, `VIDEO_SOURCE_TYPE`, and `String`, respectively. Save the variables for use when loading the view later.
       2. Create a local view. In the local view, if the UID is 0, a value is randomly assigned by the SDK, and the video source type is `VIDEO_SOURCE_CAMERA_PRIMARY` (the first camera).
       3. Create a remote view. In the remote view, the `uid` is the uid sent from the remote end, and the video source type is `VIDEO_SOURCE_REMOTE` (remote video obtained from the network).

    2. Create and implement the **ReleaseVideoView** function to release the view when a local or remote user leaves the channel.

       ![Implement Release Video View](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-12.png)

       1. In this function, create `SavedUID`, `SavedSourceType`, and `SavedChannelID` local variables, set the **Variable Type** to `Integer64`, `VIDEO_SOURCE_TYPE`, and `String`, respectively. Save the variables for use when releasing the view later.
       2. Release the local view.
       3. Release the remote view.

    ### Implement callback function [#implement-callback-function]

    Configure the previously created `onJoinChannelSuccess`, `onLeaveChannel`, `onUserJoined`, and `onUserOffline` callback functions as follows:

    1. After the local user successfully joins the channel, the `onJoinChannelSuccess` callback is triggered and the local view is created. The `uid` is set to `0` by default, but it is assigned a random value by the SDK. The video source type is `VIDEO_SOURCE_CAMERA_PRIMARY` (first camera):

       ![Join Channel Success Callback](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-13.png)

    2. After the local user leaves the channel, the `onLeaveChannel` callback is triggered which releases the local view:

       ![Leave Channel Callback](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-14.png)

    3. When a remote user joins the channel, the `onUserJoined` callback is triggeredd to create a remote view. `uid` is the uid sent from the remote end, and the video source type is `VIDEO_SOURCE_REMOTE` (remote video obtained from the network):

       ![Remote User Join Channel Callback](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-15.png)

    4. When a remote user leaves the channel, the `onUserOffline` callback is triggered to release the remote view:

       ![Remote User Leave Channel Callback](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-16.png)

    ### Leave the channel and release resources [#leave-the-channel-and-release-resources]

    To leave the channel, implement the following steps:

    1. Leave the channel.
    2. Unregister Agora backend event callback.
    3. Destroy `IRtcEngineObject` to release all resources used by Agora SDK.

    Refer to the figure below to implement the `UnInitRtcEngine` function:

    ![Leave Channel and Release Resources](https://assets-docs.agora.io/images/video-sdk/quickstart-implementation-blueprint-17.png)

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

    Take the following steps to test the sample code:

    1. In the **Load Agora Config** function, fill in the app ID, channel name, and temporary token for your project.

    2. In the Unreal Editor, click Play to run your project. Click **JoinChannel** to join a channel.

    3. Invite a friend to run the demo client on a second device. Use the same app ID, channel name, and token to join. After your friend joins successfully, you can hear and see each other.

    ## Reference [#reference-11]

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

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

    ### Next steps [#next-steps-11]

    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](/en/realtime-media/video/build/authenticate-users/authentication-workflow).

    ### Sample project [#sample-project-11]

    Agora provides open source sample projects on [GitHub](https://github.com/AgoraIO-Extensions/Agora-Unreal-RTC-SDK/tree/main/Agora-Unreal-SDK-Blueprint-Example) for your reference. Download or view the [JoinChannelVideo](https://github.com/AgoraIO-Extensions/Agora-Unreal-RTC-SDK/tree/main/Agora-Unreal-SDK-Blueprint-Example/Content/API-Example/Basic/JoinChannelVideo) project for a more detailed example.

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

    * [How can I listen for audience joining or leaving a channel?](/en/api-reference/faq/integration/audience_event)
    * [How can I fix black screen issues?](/en/api-reference/faq/quality/video_blank#black-screen-on-the-local-side)
    * [Why can't I turn on the camera?](/en/api-reference/faq/quality/video_camera)
    * [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-10]

    * [SDK error codes](/en/realtime-media/video/reference/error-codes)
    * [Connection status management](/en/realtime-media/video/build/manage-connection-and-quality/connection-status-management)

    <_PlatformProcessedMarker close="true" />
  </_PlatformPanel>
</_PlatformTabsGroup>
